Skip to content

Commit

Permalink
Improve drag tab UI (#2893)
Browse files Browse the repository at this point in the history
### What

This PR includes the following:
- Improves the UI of the dragged tab UI. Now it feels that the actual
tab is being dragged, no some different looking thing. I applied 0.5
transparency on it.
- Styles the source and destination areas during the drag (actual
styling TBD).
- Syncs with egui_tiles's master and includes a `[patch]`, so a release
of egui_tiles is needed soon after we merge this:
  - rerun-io/egui_tiles#16
  - rerun-io/egui_tiles#17
  - rerun-io/egui_tiles#18

TODO:
- [x] fine tune overlay colours
- [x] make overlay colours customisable in egui_tiles
- [x] cleanup code (currently lots of redundancy)

>[!NOTE]
>Do not review commit-by-commit, I messed my rebase/merges


![image](https://github.com/rerun-io/rerun/assets/49431240/285f2542-098c-40aa-9715-367eb8e4c917)


### Checklist
* [x] I have read and agree to [Contributor
Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and
the [Code of
Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md)
* [x] I've included a screenshot or gif (if applicable)
* [x] I have tested [demo.rerun.io](https://demo.rerun.io/pr/2893) (if
applicable)

- [PR Build Summary](https://build.rerun.io/pr/2893)
- [Docs
preview](https://rerun.io/preview/pr%3Aantoine%2Ftab-drag-ui/docs)
- [Examples
preview](https://rerun.io/preview/pr%3Aantoine%2Ftab-drag-ui/examples)
  • Loading branch information
abey79 authored Aug 2, 2023
1 parent eb3fe49 commit 8d8bc6c
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 93 deletions.
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,12 @@ panic = "abort" # This leads to better optimizations and smaller binaries (and i
[profile.bench]
debug = true


[patch.crates-io]
# Try to avoid patching crates! It prevents us from publishing the crates on crates.io.
# If you do patch always prefer to patch to a commit on the trunk of the upstream repo.
# If that is not possible, patch to a branch that has a PR open on the upstream repo.
# As a last resport, patch with a commit to our own repository.
# ALWAYS document what PR the commit hash is part of, or when it was merged into the upstream trunk.

# Temporary patch until next egui_tiles release
egui_tiles = { git = "https://github.com/rerun-io/egui_tiles", commit = "f835c4df1cc260a58122a8d37c7c3738902b9643" }
1 change: 1 addition & 0 deletions crates/re_ui/examples/re_ui_example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@ impl egui_tiles::Behavior<Tab> for MyTileTreeBehavior {
fn tab_outline_stroke(
&self,
_visuals: &egui::Visuals,
_tiles: &egui_tiles::Tiles<Tab>,
_tile_id: egui_tiles::TileId,
_active: bool,
) -> egui::Stroke {
Expand Down
238 changes: 148 additions & 90 deletions crates/re_viewport/src/viewport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use std::collections::BTreeMap;

use ahash::HashMap;
use egui_tiles::Behavior;
use re_ui::ReUi;

use re_viewer_context::{
Expand Down Expand Up @@ -248,21 +249,7 @@ impl<'a, 'b> egui_tiles::Behavior<SpaceViewId> for TabViewer<'a, 'b> {
return "internal_error".into();
};

let mut text =
egui::WidgetText::RichText(egui::RichText::new(space_view.display_name.clone()));

if self
.ctx
.selection()
.contains(&Item::SpaceView(*space_view_id))
{
// Show that it is selected:
let egui_ctx = &self.ctx.re_ui.egui_ctx;
let selection_bg_color = egui_ctx.style().visuals.selection.bg_fill;
text = text.background_color(selection_bg_color);
}

text
egui::WidgetText::RichText(egui::RichText::new(space_view.display_name.clone()))
}

#[allow(clippy::fn_params_excessive_bools)]
Expand All @@ -275,14 +262,7 @@ impl<'a, 'b> egui_tiles::Behavior<SpaceViewId> for TabViewer<'a, 'b> {
active: bool,
is_being_dragged: bool,
) -> egui::Response {
// make sure we have a space view to work with or else delegate to the default
// implementation
let space_view = if let Some(egui_tiles::Tile::Pane(space_view_id)) = tiles.get(tile_id) {
self.space_views.get(space_view_id)
} else {
None
};
let Some(space_view) = space_view else {
let Some(tab_widget) = TabWidget::new(self, ui, tiles, tile_id, active, 1.0) else {
return egui_tiles::Behavior::<SpaceViewId>::tab_ui(
self,
tiles,
Expand All @@ -293,68 +273,46 @@ impl<'a, 'b> egui_tiles::Behavior<SpaceViewId> for TabViewer<'a, 'b> {
is_being_dragged,
);
};
let space_view_id = space_view.id;

// tab icon
let icon = space_view.class(self.ctx.space_view_class_registry).icon();
let image = self.ctx.re_ui.icon_image(icon);
let texture_id = image.texture_id(ui.ctx());
let icon_size = ReUi::small_icon_size();
let icon_width_plus_padding = icon_size.x + ReUi::text_to_icon_padding();

// tab title
let text = self.tab_title_for_tile(tiles, tile_id);
let font_id = egui::TextStyle::Button.resolve(ui.style());
let galley = text.into_galley(ui, Some(false), f32::INFINITY, font_id);

let x_margin = self.tab_title_spacing(ui.visuals());
let (_, rect) = ui.allocate_space(egui::vec2(
galley.size().x + 2.0 * x_margin + icon_width_plus_padding,
ui.available_height(),
));
let galley_rect = egui::Rect::from_two_pos(
rect.min + egui::vec2(icon_width_plus_padding, 0.0),
rect.max,
);
let icon_rect = egui::Rect::from_center_size(
egui::pos2(rect.left() + x_margin + icon_size.x / 2.0, rect.center().y),
icon_size,
);
let response = ui.interact(rect, id, egui::Sense::click_and_drag());
let response = ui.interact(tab_widget.rect, id, egui::Sense::click_and_drag());

// Show a gap when dragged
if ui.is_rect_visible(rect) && !is_being_dragged {
let selected = self
.ctx
.selection()
.contains(&Item::SpaceView(space_view_id));

if selected {
ui.painter().rect(
rect,
0.0,
ui.visuals().selection.bg_fill,
egui::Stroke::NONE,
);
}
if ui.is_rect_visible(tab_widget.rect) && !is_being_dragged {
tab_widget.ui(ui);
}

let text_color = self.tab_text_color(ui.visuals(), tile_id, active);
self.on_tab_button(tiles, tile_id, &response);

let icon_image = egui::widgets::Image::new(texture_id, icon_size).tint(text_color);
icon_image.paint_at(ui, icon_rect);
response
}

ui.painter().galley_with_color(
egui::Align2::CENTER_CENTER
.align_size_within_rect(galley.size(), galley_rect)
.min,
galley.galley,
text_color,
fn drag_ui(
&mut self,
tiles: &egui_tiles::Tiles<SpaceViewId>,
ui: &mut egui::Ui,
tile_id: egui_tiles::TileId,
) {
let Some(tab_widget) = TabWidget::new(self, ui, tiles, tile_id, true, 0.5) else {
return egui_tiles::Behavior::<SpaceViewId>::drag_ui(
self,
tiles,
ui,
tile_id,
);
}
};

self.on_tab_button(tiles, tile_id, &response);
let frame = egui::Frame {
inner_margin: egui::Margin::same(0.),
outer_margin: egui::Margin::same(0.),
rounding: egui::Rounding::none(),
shadow: Default::default(),
fill: egui::Color32::TRANSPARENT,
stroke: egui::Stroke::NONE,
};

response
frame.show(ui, |ui| {
tab_widget.ui(ui);
});
}

fn on_tab_button(
Expand Down Expand Up @@ -429,22 +387,18 @@ impl<'a, 'b> egui_tiles::Behavior<SpaceViewId> for TabViewer<'a, 'b> {
self.ctx.re_ui.design_tokens.tab_bar_color
}

fn tab_bg_color(
&self,
_visuals: &egui::Visuals,
_tile_id: egui_tiles::TileId,
_active: bool,
) -> egui::Color32 {
egui::Color32::TRANSPARENT
fn dragged_overlay_color(&self, visuals: &egui::Visuals) -> egui::Color32 {
visuals.panel_fill.gamma_multiply(0.5)
}

fn tab_outline_stroke(
&self,
_visuals: &egui::Visuals,
_tile_id: egui_tiles::TileId,
_active: bool,
) -> egui::Stroke {
egui::Stroke::NONE
/// When drag-and-dropping a tile, the candidate area is drawn with this stroke.
fn drag_preview_stroke(&self, _visuals: &egui::Visuals) -> egui::Stroke {
egui::Stroke::new(1.0, egui::Color32::WHITE.gamma_multiply(0.5))
}

/// When drag-and-dropping a tile, the candidate area is drawn with this background color.
fn drag_preview_color(&self, _visuals: &egui::Visuals) -> egui::Color32 {
egui::Color32::WHITE.gamma_multiply(0.1)
}

/// The height of the bar holding tab titles.
Expand Down Expand Up @@ -477,3 +431,107 @@ fn space_view_ui(

space_view_blueprint.scene_ui(space_view_state, ctx, ui, latest_at, space_view_highlights);
}

struct TabWidget {
texture_id: egui::TextureId,
galley: egui::widget_text::WidgetTextGalley,
rect: egui::Rect,
galley_rect: egui::Rect,
icon_size: egui::Vec2,
icon_rect: egui::Rect,
bg_color: egui::Color32,
text_color: egui::Color32,
}

impl TabWidget {
fn new<'a>(
tab_viewer: &'a mut TabViewer<'_, '_>,
ui: &'a mut egui::Ui,
tiles: &'a egui_tiles::Tiles<SpaceViewId>,
tile_id: egui_tiles::TileId,
active: bool,
gamma: f32,
) -> Option<Self> {
// make sure we have a space view to work with
let space_view = if let Some(egui_tiles::Tile::Pane(space_view_id)) = tiles.get(tile_id) {
tab_viewer.space_views.get(space_view_id)
} else {
return None;
};
let Some(space_view) = space_view else {
return None
};
let space_view_id = space_view.id;

// tab icon
let icon = space_view
.class(tab_viewer.ctx.space_view_class_registry)
.icon();
let image = tab_viewer.ctx.re_ui.icon_image(icon);
let texture_id = image.texture_id(ui.ctx());

let icon_size = ReUi::small_icon_size();
let icon_width_plus_padding = icon_size.x + ReUi::text_to_icon_padding();

// tab title
let text = tab_viewer.tab_title_for_tile(tiles, tile_id);
let font_id = egui::TextStyle::Button.resolve(ui.style());
let galley = text.into_galley(ui, Some(false), f32::INFINITY, font_id);

let x_margin = tab_viewer.tab_title_spacing(ui.visuals());
let (_, rect) = ui.allocate_space(egui::vec2(
galley.size().x + 2.0 * x_margin + icon_width_plus_padding,
ReUi::title_bar_height(),
));
let galley_rect = egui::Rect::from_two_pos(
rect.min + egui::vec2(icon_width_plus_padding, 0.0),
rect.max,
);
let icon_rect = egui::Rect::from_center_size(
egui::pos2(rect.left() + x_margin + icon_size.x / 2.0, rect.center().y),
icon_size,
);

let selected = tab_viewer
.ctx
.selection()
.contains(&Item::SpaceView(space_view_id));
let bg_color = if selected {
ui.visuals().selection.bg_fill
} else {
tab_viewer.tab_bar_color(ui.visuals())
};
let bg_color = bg_color.gamma_multiply(gamma);
let text_color = tab_viewer
.tab_text_color(ui.visuals(), tiles, tile_id, active)
.gamma_multiply(gamma);

Some(Self {
texture_id,
galley,
rect,
galley_rect,
icon_size,
icon_rect,
bg_color,
text_color,
})
}

fn ui(self, ui: &mut egui::Ui) {
ui.painter()
.rect(self.rect, 0.0, self.bg_color, egui::Stroke::NONE);

let icon_image =
egui::widgets::Image::new(self.texture_id, self.icon_size).tint(self.text_color);
icon_image.paint_at(ui, self.icon_rect);

ui.painter().galley_with_color(
egui::Align2::CENTER_CENTER
.align_size_within_rect(self.galley.size(), self.galley_rect)
.min,
self.galley.galley,
self.text_color,
);
}
}

0 comments on commit 8d8bc6c

Please sign in to comment.