From 2827675686b3fd07cc3de52462ed91040479a98a Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 23 May 2023 13:49:57 +1000 Subject: [PATCH 01/33] feat: scrolling ui line --- examples/advanced.rs | 6 +++ src/container/tabs.rs | 86 ++++++++++++++++++++++--------------------- 2 files changed, 51 insertions(+), 41 deletions(-) diff --git a/examples/advanced.rs b/examples/advanced.rs index c2285dd..3b92ad9 100644 --- a/examples/advanced.rs +++ b/examples/advanced.rs @@ -1,6 +1,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release use eframe::egui; +use egui::Vec2; fn main() -> Result<(), eframe::Error> { env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). @@ -138,6 +139,11 @@ impl egui_tiles::Behavior for TreeBehavior { if ui.button("➕").clicked() { self.add_child_to = Some(tile_id); } + + if ui.button(">").clicked() { + let initial_delta = Vec2::new(5.0, 0.0); + ui.scroll_with_delta(initial_delta) + } } // --- diff --git a/src/container/tabs.rs b/src/container/tabs.rs index 04adc66..5bd7c39 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -1,4 +1,4 @@ -use egui::{vec2, Rect}; +use egui::{vec2, Rect, Vec2, ScrollArea, scroll_area::ScrollBarVisibility}; use crate::{ is_being_dragged, Behavior, ContainerInsertion, DropContext, InsertionPoint, SimplifyAction, @@ -98,55 +98,59 @@ impl Tabs { ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { // Add buttons such as "add new tab" - behavior.top_bar_rtl_ui(&tree.tiles, ui, tile_id, self); - ui.spacing_mut().item_spacing.x = 0.0; // Tabs have spacing built-in - - ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { - ui.set_clip_rect(ui.max_rect()); // Don't cover the `rtl_ui` buttons. - - if !tree.is_root(tile_id) { - // Make the background behind the buttons draggable (to drag the parent container tile): - if ui - .interact( - ui.max_rect(), - ui.id().with("background"), - egui::Sense::drag(), - ) - .on_hover_cursor(egui::CursorIcon::Grab) - .drag_started() - { - ui.memory_mut(|mem| mem.set_dragged_id(tile_id.id())); + behavior.top_bar_rtl_ui(&tree.tiles, ui, tile_id, self); + ui.set_clip_rect(ui.available_rect_before_wrap()); // Don't cover the `rtl_ui` buttons. + + let area = egui::ScrollArea::new([true, false]) + .scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden) + .max_width(ui.available_width()); + + area.show(ui, |ui| { + ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { + if !tree.is_root(tile_id) { + // Make the background behind the buttons draggable (to drag the parent container tile): + if ui + .interact( + ui.max_rect(), + ui.id().with("background"), + egui::Sense::drag(), + ) + .on_hover_cursor(egui::CursorIcon::Grab) + .drag_started() + { + ui.memory_mut(|mem| mem.set_dragged_id(tile_id.id())); + } } - } - for (i, &child_id) in self.children.iter().enumerate() { - let is_being_dragged = is_being_dragged(ui.ctx(), child_id); + for (i, &child_id) in self.children.iter().enumerate() { + let is_being_dragged = is_being_dragged(ui.ctx(), child_id); - let selected = self.is_active(child_id); - let id = child_id.id(); + let selected = self.is_active(child_id); + let id = child_id.id(); - let response = - behavior.tab_ui(&tree.tiles, ui, id, child_id, selected, is_being_dragged); - let response = response.on_hover_cursor(egui::CursorIcon::Grab); - if response.clicked() { - next_active = Some(child_id); - } - - if let Some(mouse_pos) = drop_context.mouse_pos { - if drop_context.dragged_tile_id.is_some() - && response.rect.contains(mouse_pos) - { - // Expand this tab - maybe the user wants to drop something into it! + let response = + behavior.tab_ui(&tree.tiles, ui, id, child_id, selected, is_being_dragged); + let response = response.on_hover_cursor(egui::CursorIcon::Grab); + if response.clicked() { next_active = Some(child_id); } - } - button_rects.insert(child_id, response.rect); - if is_being_dragged { - dragged_index = Some(i); + if let Some(mouse_pos) = drop_context.mouse_pos { + if drop_context.dragged_tile_id.is_some() + && response.rect.contains(mouse_pos) + { + // Expand this tab - maybe the user wants to drop something into it! + next_active = Some(child_id); + } + } + + button_rects.insert(child_id, response.rect); + if is_being_dragged { + dragged_index = Some(i); + } } - } + }); }); }); From 856d437d2de1d390f49f42dd905a43a47e4f0f63 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 23 May 2023 15:43:27 +1000 Subject: [PATCH 02/33] feat: scrolling with chevrons + click to focus --- examples/advanced.rs | 26 ++++++++++++++++++-- src/behavior.rs | 17 +++++++++++++ src/container/tabs.rs | 57 ++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 94 insertions(+), 6 deletions(-) diff --git a/examples/advanced.rs b/examples/advanced.rs index 3b92ad9..3b09e29 100644 --- a/examples/advanced.rs +++ b/examples/advanced.rs @@ -1,5 +1,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +use std::sync::mpsc::Sender; + use eframe::egui; use egui::Vec2; @@ -129,20 +131,40 @@ impl egui_tiles::Behavior for TreeBehavior { format!("View {}", view.nr).into() } + fn top_bar_ltl_ui( + &mut self, + _tiles: &egui_tiles::Tiles, + ui: &mut egui::Ui, + tile_id: egui_tiles::TileId, + _tabs: &egui_tiles::Tabs, + _scroll: Sender, + _offset: Option + ) { + // if _offset.is_some() && _offset.unwrap() > 45.0 { + if ui.button("<").clicked() { + _scroll.send(-45.0).unwrap(); + } + // } + } + fn top_bar_rtl_ui( &mut self, _tiles: &egui_tiles::Tiles, ui: &mut egui::Ui, tile_id: egui_tiles::TileId, _tabs: &egui_tiles::Tabs, + _scroll: Sender, + _offset: Option ) { if ui.button("➕").clicked() { self.add_child_to = Some(tile_id); } if ui.button(">").clicked() { - let initial_delta = Vec2::new(5.0, 0.0); - ui.scroll_with_delta(initial_delta) + // Float value to move scroll by + // +'ve is right + // -'ve is left + _scroll.send(45.0).unwrap(); } } diff --git a/src/behavior.rs b/src/behavior.rs index 68662de..b38c743 100644 --- a/src/behavior.rs +++ b/src/behavior.rs @@ -1,3 +1,5 @@ +use std::sync::mpsc::Sender; + use egui::{ vec2, Color32, Id, Rect, Response, Rgba, Sense, Stroke, TextStyle, Ui, Visuals, WidgetText, }; @@ -113,6 +115,21 @@ pub trait Behavior { _ui: &mut Ui, _tile_id: TileId, _tabs: &crate::Tabs, + _scroll: Sender, + _offset: Option + ) { + // if ui.button("➕").clicked() { + // } + } + + fn top_bar_ltl_ui( + &mut self, + _tiles: &Tiles, + _ui: &mut Ui, + _tile_id: TileId, + _tabs: &crate::Tabs, + _scroll: Sender, + _offset: Option ) { // if ui.button("➕").clicked() { // } diff --git a/src/container/tabs.rs b/src/container/tabs.rs index 5bd7c39..798a715 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -1,4 +1,4 @@ -use egui::{vec2, Rect, Vec2, ScrollArea, scroll_area::ScrollBarVisibility}; +use egui::{vec2, Rect, scroll_area::ScrollBarVisibility}; use crate::{ is_being_dragged, Behavior, ContainerInsertion, DropContext, InsertionPoint, SimplifyAction, @@ -85,6 +85,20 @@ impl Tabs { tile_id: TileId, ) -> Option { let mut next_active = self.active; + + let scroll_length: f32 = 0.0; + + let id = ui.make_persistent_id(tile_id); + + println!("START:: LOC:{{{:?}}}, VAL:{:?}", id, 0.0); + ui.ctx().memory_mut( + |m| + if m.data.get_temp::(id).is_none() { + m.data.insert_temp(id, scroll_length) + }else { + println!("Received: {:?}", m.data.get_temp::(id).unwrap()); + } + ); let tab_bar_height = behavior.tab_bar_height(ui.style()); let tab_bar_rect = rect.split_top_bottom_at_y(rect.top() + tab_bar_height).0; @@ -99,14 +113,48 @@ impl Tabs { ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { // Add buttons such as "add new tab" ui.spacing_mut().item_spacing.x = 0.0; // Tabs have spacing built-in - behavior.top_bar_rtl_ui(&tree.tiles, ui, tile_id, self); + + let scrolling_channel = std::sync::mpsc::channel::(); + let offset = ui.ctx().memory_mut( + |m| + m.data.get_temp::(id) + ); + + behavior.top_bar_rtl_ui(&tree.tiles, ui, tile_id, self, scrolling_channel.0.clone(), offset); + behavior.top_bar_ltl_ui(&tree.tiles, ui, tile_id, self, scrolling_channel.0, offset); + ui.set_clip_rect(ui.available_rect_before_wrap()); // Don't cover the `rtl_ui` buttons. - let area = egui::ScrollArea::new([true, false]) + let mut area = egui::ScrollArea::new([true, false]) .scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden) .max_width(ui.available_width()); - area.show(ui, |ui| { + if let Ok(new_position) = scrolling_channel.1.try_recv() { + if let Some(offset) = offset { + let mut offset = offset; + + println!("GOT UPDATE: {}:[{}] FROM LOC:{{{:?}}} MAX:{}", offset, new_position, id, ui.available_width()); + offset += new_position; + + // Max is: [`ui.available_width()`] + if offset >= (ui.available_width()) { + offset = ui.available_width(); + } + + if offset < 0.0 { + offset = 0.0; + } + + area = area.to_owned().horizontal_scroll_offset(offset); + + ui.ctx().memory_mut( + |m| + m.data.insert_temp(id, offset) + ); + } + } + + area.to_owned().show(ui, |ui| { ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { if !tree.is_root(tile_id) { // Make the background behind the buttons draggable (to drag the parent container tile): @@ -134,6 +182,7 @@ impl Tabs { let response = response.on_hover_cursor(egui::CursorIcon::Grab); if response.clicked() { next_active = Some(child_id); + response.scroll_to_me(None) } if let Some(mouse_pos) = drop_context.mouse_pos { From 277b716092c9a40d6a30213117b47076a020c846 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 23 May 2023 15:46:56 +1000 Subject: [PATCH 03/33] mod: cleanup --- examples/advanced.rs | 6 ++---- src/container/tabs.rs | 4 ---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/examples/advanced.rs b/examples/advanced.rs index 3b09e29..9278ab2 100644 --- a/examples/advanced.rs +++ b/examples/advanced.rs @@ -135,16 +135,14 @@ impl egui_tiles::Behavior for TreeBehavior { &mut self, _tiles: &egui_tiles::Tiles, ui: &mut egui::Ui, - tile_id: egui_tiles::TileId, + _tile_id: egui_tiles::TileId, _tabs: &egui_tiles::Tabs, _scroll: Sender, _offset: Option ) { - // if _offset.is_some() && _offset.unwrap() > 45.0 { if ui.button("<").clicked() { _scroll.send(-45.0).unwrap(); } - // } } fn top_bar_rtl_ui( @@ -161,7 +159,7 @@ impl egui_tiles::Behavior for TreeBehavior { } if ui.button(">").clicked() { - // Float value to move scroll by + // Integer value to move scroll by // +'ve is right // -'ve is left _scroll.send(45.0).unwrap(); diff --git a/src/container/tabs.rs b/src/container/tabs.rs index 798a715..afe9147 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -90,13 +90,10 @@ impl Tabs { let id = ui.make_persistent_id(tile_id); - println!("START:: LOC:{{{:?}}}, VAL:{:?}", id, 0.0); ui.ctx().memory_mut( |m| if m.data.get_temp::(id).is_none() { m.data.insert_temp(id, scroll_length) - }else { - println!("Received: {:?}", m.data.get_temp::(id).unwrap()); } ); @@ -133,7 +130,6 @@ impl Tabs { if let Some(offset) = offset { let mut offset = offset; - println!("GOT UPDATE: {}:[{}] FROM LOC:{{{:?}}} MAX:{}", offset, new_position, id, ui.available_width()); offset += new_position; // Max is: [`ui.available_width()`] From 3655053270c5d0d779f0067523241a5724674d7c Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 24 May 2023 23:33:55 +1000 Subject: [PATCH 04/33] mod: cargo fmt --- examples/advanced.rs | 6 +++--- src/behavior.rs | 4 ++-- src/container/tabs.rs | 43 +++++++++++++++++++++++++------------------ 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/examples/advanced.rs b/examples/advanced.rs index 9278ab2..3a612f2 100644 --- a/examples/advanced.rs +++ b/examples/advanced.rs @@ -138,11 +138,11 @@ impl egui_tiles::Behavior for TreeBehavior { _tile_id: egui_tiles::TileId, _tabs: &egui_tiles::Tabs, _scroll: Sender, - _offset: Option + _offset: Option, ) { if ui.button("<").clicked() { _scroll.send(-45.0).unwrap(); - } + } } fn top_bar_rtl_ui( @@ -152,7 +152,7 @@ impl egui_tiles::Behavior for TreeBehavior { tile_id: egui_tiles::TileId, _tabs: &egui_tiles::Tabs, _scroll: Sender, - _offset: Option + _offset: Option, ) { if ui.button("➕").clicked() { self.add_child_to = Some(tile_id); diff --git a/src/behavior.rs b/src/behavior.rs index b38c743..3a23255 100644 --- a/src/behavior.rs +++ b/src/behavior.rs @@ -116,7 +116,7 @@ pub trait Behavior { _tile_id: TileId, _tabs: &crate::Tabs, _scroll: Sender, - _offset: Option + _offset: Option, ) { // if ui.button("➕").clicked() { // } @@ -129,7 +129,7 @@ pub trait Behavior { _tile_id: TileId, _tabs: &crate::Tabs, _scroll: Sender, - _offset: Option + _offset: Option, ) { // if ui.button("➕").clicked() { // } diff --git a/src/container/tabs.rs b/src/container/tabs.rs index afe9147..ef3ba4a 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -1,4 +1,4 @@ -use egui::{vec2, Rect, scroll_area::ScrollBarVisibility}; +use egui::{scroll_area::ScrollBarVisibility, vec2, Rect, Sense}; use crate::{ is_being_dragged, Behavior, ContainerInsertion, DropContext, InsertionPoint, SimplifyAction, @@ -85,17 +85,16 @@ impl Tabs { tile_id: TileId, ) -> Option { let mut next_active = self.active; - + let scroll_length: f32 = 0.0; let id = ui.make_persistent_id(tile_id); - ui.ctx().memory_mut( - |m| + ui.ctx().memory_mut(|m| { if m.data.get_temp::(id).is_none() { m.data.insert_temp(id, scroll_length) } - ); + }); let tab_bar_height = behavior.tab_bar_height(ui.style()); let tab_bar_rect = rect.split_top_bottom_at_y(rect.top() + tab_bar_height).0; @@ -112,12 +111,16 @@ impl Tabs { ui.spacing_mut().item_spacing.x = 0.0; // Tabs have spacing built-in let scrolling_channel = std::sync::mpsc::channel::(); - let offset = ui.ctx().memory_mut( - |m| - m.data.get_temp::(id) + let offset = ui.ctx().memory_mut(|m| m.data.get_temp::(id)); + + behavior.top_bar_rtl_ui( + &tree.tiles, + ui, + tile_id, + self, + scrolling_channel.0.clone(), + offset, ); - - behavior.top_bar_rtl_ui(&tree.tiles, ui, tile_id, self, scrolling_channel.0.clone(), offset); behavior.top_bar_ltl_ui(&tree.tiles, ui, tile_id, self, scrolling_channel.0, offset); ui.set_clip_rect(ui.available_rect_before_wrap()); // Don't cover the `rtl_ui` buttons. @@ -140,17 +143,15 @@ impl Tabs { if offset < 0.0 { offset = 0.0; } - - area = area.to_owned().horizontal_scroll_offset(offset); - ui.ctx().memory_mut( - |m| - m.data.insert_temp(id, offset) - ); + area = area.to_owned().horizontal_scroll_offset(offset); + + ui.ctx().memory_mut(|m| m.data.insert_temp(id, offset)); } } area.to_owned().show(ui, |ui| { + // ui.interact(rect, id, Sense::) ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { if !tree.is_root(tile_id) { // Make the background behind the buttons draggable (to drag the parent container tile): @@ -173,8 +174,14 @@ impl Tabs { let selected = self.is_active(child_id); let id = child_id.id(); - let response = - behavior.tab_ui(&tree.tiles, ui, id, child_id, selected, is_being_dragged); + let response = behavior.tab_ui( + &tree.tiles, + ui, + id, + child_id, + selected, + is_being_dragged, + ); let response = response.on_hover_cursor(egui::CursorIcon::Grab); if response.clicked() { next_active = Some(child_id); From 0808cd40fc636c1fbe57ed7b075986f465445a99 Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 24 May 2023 23:36:52 +1000 Subject: [PATCH 05/33] mod: checks --- examples/advanced.rs | 2 -- src/container/tabs.rs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/advanced.rs b/examples/advanced.rs index 3a612f2..9c0c9c9 100644 --- a/examples/advanced.rs +++ b/examples/advanced.rs @@ -1,9 +1,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release use std::sync::mpsc::Sender; - use eframe::egui; -use egui::Vec2; fn main() -> Result<(), eframe::Error> { env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). diff --git a/src/container/tabs.rs b/src/container/tabs.rs index ef3ba4a..e26272d 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -1,4 +1,4 @@ -use egui::{scroll_area::ScrollBarVisibility, vec2, Rect, Sense}; +use egui::{scroll_area::ScrollBarVisibility, vec2, Rect}; use crate::{ is_being_dragged, Behavior, ContainerInsertion, DropContext, InsertionPoint, SimplifyAction, From ffeb56fe07757130a1e4f0d030679842eabc1b66 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 1 Jun 2023 20:26:21 +1000 Subject: [PATCH 06/33] feat: hide/show icons based on scroll type [right not positioned] --- examples/advanced.rs | 17 ++++---- src/behavior.rs | 4 +- src/container/linear.rs | 2 +- src/container/tabs.rs | 86 +++++++++++++++++++++++++---------------- 4 files changed, 65 insertions(+), 44 deletions(-) diff --git a/examples/advanced.rs b/examples/advanced.rs index 9c0c9c9..1605b8e 100644 --- a/examples/advanced.rs +++ b/examples/advanced.rs @@ -1,6 +1,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release use std::sync::mpsc::Sender; + use eframe::egui; fn main() -> Result<(), eframe::Error> { @@ -135,8 +136,8 @@ impl egui_tiles::Behavior for TreeBehavior { ui: &mut egui::Ui, _tile_id: egui_tiles::TileId, _tabs: &egui_tiles::Tabs, - _scroll: Sender, - _offset: Option, + _offset: f32, + _scroll: Sender ) { if ui.button("<").clicked() { _scroll.send(-45.0).unwrap(); @@ -147,14 +148,14 @@ impl egui_tiles::Behavior for TreeBehavior { &mut self, _tiles: &egui_tiles::Tiles, ui: &mut egui::Ui, - tile_id: egui_tiles::TileId, + _tile_id: egui_tiles::TileId, _tabs: &egui_tiles::Tabs, - _scroll: Sender, - _offset: Option, + _offset: f32, + _scroll: Sender ) { - if ui.button("➕").clicked() { - self.add_child_to = Some(tile_id); - } + // if ui.button("➕").clicked() { + // self.add_child_to = Some(tile_id); + // } if ui.button(">").clicked() { // Integer value to move scroll by diff --git a/src/behavior.rs b/src/behavior.rs index 3a23255..112dfbe 100644 --- a/src/behavior.rs +++ b/src/behavior.rs @@ -115,8 +115,8 @@ pub trait Behavior { _ui: &mut Ui, _tile_id: TileId, _tabs: &crate::Tabs, + _offset: f32, _scroll: Sender, - _offset: Option, ) { // if ui.button("➕").clicked() { // } @@ -128,8 +128,8 @@ pub trait Behavior { _ui: &mut Ui, _tile_id: TileId, _tabs: &crate::Tabs, + _offset: f32, _scroll: Sender, - _offset: Option, ) { // if ui.button("➕").clicked() { // } diff --git a/src/container/linear.rs b/src/container/linear.rs index c609c54..3c3bfb3 100644 --- a/src/container/linear.rs +++ b/src/container/linear.rs @@ -110,7 +110,7 @@ impl Linear { /// /// The `fraction` is the fraction of the total width that the first child should get. pub fn new_binary(dir: LinearDir, children: [TileId; 2], fraction: f32) -> Self { - debug_assert!(0.0 <= fraction && fraction <= 1.0); + debug_assert!((0.0..=1.0).contains(&fraction)); let mut slf = Self { children: children.into(), dir, diff --git a/src/container/tabs.rs b/src/container/tabs.rs index e26272d..7e02220 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -1,4 +1,4 @@ -use egui::{scroll_area::ScrollBarVisibility, vec2, Rect}; +use egui::{scroll_area::ScrollBarVisibility, vec2, Rect, Vec2}; use crate::{ is_being_dragged, Behavior, ContainerInsertion, DropContext, InsertionPoint, SimplifyAction, @@ -15,6 +15,13 @@ pub struct Tabs { pub active: Option, } +#[derive(Default, Clone)] +struct ScrollState { + pub offset: Vec2, + pub consumed: Vec2, + pub available: Vec2 +} + impl Tabs { pub fn new(children: Vec) -> Self { let active = children.first().copied(); @@ -86,13 +93,12 @@ impl Tabs { ) -> Option { let mut next_active = self.active; - let scroll_length: f32 = 0.0; - + let scroll_state: ScrollState = ScrollState::default(); let id = ui.make_persistent_id(tile_id); ui.ctx().memory_mut(|m| { - if m.data.get_temp::(id).is_none() { - m.data.insert_temp(id, scroll_length) + if m.data.get_temp::(id).is_none() { + m.data.insert_temp(id, scroll_state) } }); @@ -106,22 +112,35 @@ impl Tabs { ui.painter() .rect_filled(ui.max_rect(), 0.0, behavior.tab_bar_color(ui.visuals())); - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { // Add buttons such as "add new tab" ui.spacing_mut().item_spacing.x = 0.0; // Tabs have spacing built-in let scrolling_channel = std::sync::mpsc::channel::(); - let offset = ui.ctx().memory_mut(|m| m.data.get_temp::(id)); - - behavior.top_bar_rtl_ui( - &tree.tiles, - ui, - tile_id, - self, - scrolling_channel.0.clone(), - offset, - ); - behavior.top_bar_ltl_ui(&tree.tiles, ui, tile_id, self, scrolling_channel.0, offset); + let mut scroll_state: ScrollState = ui.ctx().memory_mut(|m| m.data.get_temp::(id)).unwrap(); + + if scroll_state.offset.x > 0.0 { + behavior.top_bar_ltl_ui( + &tree.tiles, ui, tile_id, + self, scroll_state.offset.x, + scrolling_channel.0.clone() + ); + } + + // ui.allocate_space(scroll_state.available); + + let should_show_scroll_right = scroll_state.consumed.x >= scroll_state.available.x; + + if should_show_scroll_right { + behavior.top_bar_rtl_ui( + &tree.tiles, + ui, + tile_id, + self, + scroll_state.offset.x, + scrolling_channel.0 + ); + } ui.set_clip_rect(ui.available_rect_before_wrap()); // Don't cover the `rtl_ui` buttons. @@ -130,27 +149,22 @@ impl Tabs { .max_width(ui.available_width()); if let Ok(new_position) = scrolling_channel.1.try_recv() { - if let Some(offset) = offset { - let mut offset = offset; - - offset += new_position; + scroll_state.offset.x += new_position; - // Max is: [`ui.available_width()`] - if offset >= (ui.available_width()) { - offset = ui.available_width(); - } - - if offset < 0.0 { - offset = 0.0; - } - - area = area.to_owned().horizontal_scroll_offset(offset); + // Max is: [`ui.available_width()`] + if scroll_state.offset.x >= (ui.available_width()) { + scroll_state.offset.x = ui.available_width(); + } - ui.ctx().memory_mut(|m| m.data.insert_temp(id, offset)); + if scroll_state.offset.x < 0.0 { + scroll_state.offset.x = 0.0; } + + area = area.to_owned().horizontal_scroll_offset(scroll_state.offset.x); + ui.ctx().memory_mut(|m| m.data.insert_temp(id, scroll_state.clone())); } - area.to_owned().show(ui, |ui| { + let output = area.show_viewport(ui, |ui, _| { // ui.interact(rect, id, Sense::) ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { if !tree.is_root(tile_id) { @@ -204,6 +218,12 @@ impl Tabs { } }); }); + + scroll_state.offset = output.state.offset; + scroll_state.consumed = output.content_size; + scroll_state.available = output.inner_rect.size(); + + ui.ctx().memory_mut(|m| m.data.insert_temp(id, scroll_state)); }); // ----------- From 26c163fc01e403048276cd088c569d695a26382e Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 2 Jun 2023 20:33:27 +1000 Subject: [PATCH 07/33] wip: split filled layout --- examples/advanced.rs | 17 +++++----- src/behavior.rs | 8 ++--- src/container/tabs.rs | 74 ++++++++++++++++++++++++------------------- 3 files changed, 55 insertions(+), 44 deletions(-) diff --git a/examples/advanced.rs b/examples/advanced.rs index 1605b8e..690a5db 100644 --- a/examples/advanced.rs +++ b/examples/advanced.rs @@ -3,6 +3,7 @@ use std::sync::mpsc::Sender; use eframe::egui; +use egui::Button; fn main() -> Result<(), eframe::Error> { env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). @@ -130,38 +131,38 @@ impl egui_tiles::Behavior for TreeBehavior { format!("View {}", view.nr).into() } - fn top_bar_ltl_ui( + fn top_bar_left_ui( &mut self, _tiles: &egui_tiles::Tiles, ui: &mut egui::Ui, _tile_id: egui_tiles::TileId, _tabs: &egui_tiles::Tabs, _offset: f32, - _scroll: Sender + _scroll: &mut f32 ) { - if ui.button("<").clicked() { - _scroll.send(-45.0).unwrap(); + if ui.button("⏴").clicked() { + *_scroll += -45.0; } } - fn top_bar_rtl_ui( + fn top_bar_right_ui( &mut self, _tiles: &egui_tiles::Tiles, ui: &mut egui::Ui, _tile_id: egui_tiles::TileId, _tabs: &egui_tiles::Tabs, _offset: f32, - _scroll: Sender + _scroll: &mut f32 ) { // if ui.button("➕").clicked() { // self.add_child_to = Some(tile_id); // } - if ui.button(">").clicked() { + if ui.button("⏵").clicked() { // Integer value to move scroll by // +'ve is right // -'ve is left - _scroll.send(45.0).unwrap(); + *_scroll += 45.0; } } diff --git a/src/behavior.rs b/src/behavior.rs index 112dfbe..d1658bb 100644 --- a/src/behavior.rs +++ b/src/behavior.rs @@ -109,27 +109,27 @@ pub trait Behavior { /// You can use this to, for instance, add a button for adding new tabs. /// /// The widgets will be added right-to-left. - fn top_bar_rtl_ui( + fn top_bar_right_ui( &mut self, _tiles: &Tiles, _ui: &mut Ui, _tile_id: TileId, _tabs: &crate::Tabs, _offset: f32, - _scroll: Sender, + _scroll: &mut f32, ) { // if ui.button("➕").clicked() { // } } - fn top_bar_ltl_ui( + fn top_bar_left_ui( &mut self, _tiles: &Tiles, _ui: &mut Ui, _tile_id: TileId, _tabs: &crate::Tabs, _offset: f32, - _scroll: Sender, + _scroll: &mut f32, ) { // if ui.button("➕").clicked() { // } diff --git a/src/container/tabs.rs b/src/container/tabs.rs index 7e02220..86458a9 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -1,4 +1,4 @@ -use egui::{scroll_area::ScrollBarVisibility, vec2, Rect, Vec2}; +use egui::{scroll_area::ScrollBarVisibility, vec2, Rect, Vec2, Pos2}; use crate::{ is_being_dragged, Behavior, ContainerInsertion, DropContext, InsertionPoint, SimplifyAction, @@ -19,7 +19,8 @@ pub struct Tabs { struct ScrollState { pub offset: Vec2, pub consumed: Vec2, - pub available: Vec2 + pub available: Vec2, + pub offset_delta: Vec2 } impl Tabs { @@ -112,61 +113,51 @@ impl Tabs { ui.painter() .rect_filled(ui.max_rect(), 0.0, behavior.tab_bar_color(ui.visuals())); - ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { // Add buttons such as "add new tab" ui.spacing_mut().item_spacing.x = 0.0; // Tabs have spacing built-in - let scrolling_channel = std::sync::mpsc::channel::(); let mut scroll_state: ScrollState = ui.ctx().memory_mut(|m| m.data.get_temp::(id)).unwrap(); - if scroll_state.offset.x > 0.0 { - behavior.top_bar_ltl_ui( - &tree.tiles, ui, tile_id, - self, scroll_state.offset.x, - scrolling_channel.0.clone() - ); - } - - // ui.allocate_space(scroll_state.available); - - let should_show_scroll_right = scroll_state.consumed.x >= scroll_state.available.x; - - if should_show_scroll_right { - behavior.top_bar_rtl_ui( + if scroll_state.consumed.x > scroll_state.available.x { + behavior.top_bar_right_ui( &tree.tiles, ui, tile_id, self, scroll_state.offset.x, - scrolling_channel.0 + &mut scroll_state.offset_delta.x ); } ui.set_clip_rect(ui.available_rect_before_wrap()); // Don't cover the `rtl_ui` buttons. - let mut area = egui::ScrollArea::new([true, false]) - .scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden) - .max_width(ui.available_width()); + let total_width = ui.available_width(); + let mut scroll_area_size = Vec2::ZERO; + scroll_area_size.x = total_width - 25.0; + scroll_area_size.y = ui.available_height(); - if let Ok(new_position) = scrolling_channel.1.try_recv() { - scroll_state.offset.x += new_position; + let mut area = egui::ScrollArea::horizontal() + .scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden) + .max_width(ui.available_width()-25.0); + { // Max is: [`ui.available_width()`] - if scroll_state.offset.x >= (ui.available_width()) { - scroll_state.offset.x = ui.available_width(); + if scroll_state.offset_delta.x >= (ui.available_width()) { + scroll_state.offset_delta.x = ui.available_width(); } - if scroll_state.offset.x < 0.0 { - scroll_state.offset.x = 0.0; + if scroll_state.offset_delta.x < 0.0 { + scroll_state.offset_delta.x = 0.0; } - area = area.to_owned().horizontal_scroll_offset(scroll_state.offset.x); - ui.ctx().memory_mut(|m| m.data.insert_temp(id, scroll_state.clone())); + area = area.to_owned().horizontal_scroll_offset(scroll_state.offset.x + scroll_state.offset_delta.x); + + scroll_state.offset_delta = Vec2::ZERO; } let output = area.show_viewport(ui, |ui, _| { - // ui.interact(rect, id, Sense::) - ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { + ui.allocate_ui_with_layout(scroll_area_size, egui::Layout::left_to_right(egui::Align::Center), |ui| { if !tree.is_root(tile_id) { // Make the background behind the buttons draggable (to drag the parent container tile): if ui @@ -219,6 +210,25 @@ impl Tabs { }); }); + if scroll_state.offset.x > 0.0 { + + } + + let total_width = ui.available_width(); + let mut scroll_area_size = Vec2::ZERO; + scroll_area_size.x = 25.0; + scroll_area_size.y = ui.available_height(); + + println!("W: {:?}", scroll_area_size); + + ui.allocate_ui_with_layout(scroll_area_size, egui::Layout::right_to_left(egui::Align::Center), | ui | { + behavior.top_bar_left_ui( + &tree.tiles, ui, tile_id, + self, scroll_state.offset.x, + &mut scroll_state.offset_delta.x + ); + }); + scroll_state.offset = output.state.offset; scroll_state.consumed = output.content_size; scroll_state.available = output.inner_rect.size(); From 1eba30170fbcc7c6adade11ae0362ab3fde16440 Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 2 Jun 2023 20:39:59 +1000 Subject: [PATCH 08/33] wip: prev w/ clippy --- examples/advanced.rs | 3 --- src/behavior.rs | 2 -- src/container/tabs.rs | 29 ++++++++++++----------------- 3 files changed, 12 insertions(+), 22 deletions(-) diff --git a/examples/advanced.rs b/examples/advanced.rs index 690a5db..19a65d5 100644 --- a/examples/advanced.rs +++ b/examples/advanced.rs @@ -1,9 +1,6 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release -use std::sync::mpsc::Sender; - use eframe::egui; -use egui::Button; fn main() -> Result<(), eframe::Error> { env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). diff --git a/src/behavior.rs b/src/behavior.rs index d1658bb..4c53e27 100644 --- a/src/behavior.rs +++ b/src/behavior.rs @@ -1,5 +1,3 @@ -use std::sync::mpsc::Sender; - use egui::{ vec2, Color32, Id, Rect, Response, Rgba, Sense, Stroke, TextStyle, Ui, Visuals, WidgetText, }; diff --git a/src/container/tabs.rs b/src/container/tabs.rs index 86458a9..02dce48 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -1,4 +1,4 @@ -use egui::{scroll_area::ScrollBarVisibility, vec2, Rect, Vec2, Pos2}; +use egui::{scroll_area::ScrollBarVisibility, vec2, Rect, Vec2}; use crate::{ is_being_dragged, Behavior, ContainerInsertion, DropContext, InsertionPoint, SimplifyAction, @@ -211,24 +211,19 @@ impl Tabs { }); if scroll_state.offset.x > 0.0 { - + let mut scroll_area_size = Vec2::ZERO; + scroll_area_size.x = 25.0; + scroll_area_size.y = ui.available_height(); + + ui.allocate_ui_with_layout(scroll_area_size, egui::Layout::right_to_left(egui::Align::Center), | ui | { + behavior.top_bar_left_ui( + &tree.tiles, ui, tile_id, + self, scroll_state.offset.x, + &mut scroll_state.offset_delta.x + ); + }); } - let total_width = ui.available_width(); - let mut scroll_area_size = Vec2::ZERO; - scroll_area_size.x = 25.0; - scroll_area_size.y = ui.available_height(); - - println!("W: {:?}", scroll_area_size); - - ui.allocate_ui_with_layout(scroll_area_size, egui::Layout::right_to_left(egui::Align::Center), | ui | { - behavior.top_bar_left_ui( - &tree.tiles, ui, tile_id, - self, scroll_state.offset.x, - &mut scroll_state.offset_delta.x - ); - }); - scroll_state.offset = output.state.offset; scroll_state.consumed = output.content_size; scroll_state.available = output.inner_rect.size(); From 67e904d6241757e80af77f2cfffdb0360e79f4d3 Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 2 Jun 2023 21:47:55 +1000 Subject: [PATCH 09/33] mod: working version --- src/container/tabs.rs | 179 +++++++++++++++++++++++++----------------- 1 file changed, 106 insertions(+), 73 deletions(-) diff --git a/src/container/tabs.rs b/src/container/tabs.rs index 02dce48..6a1ae5b 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -1,3 +1,5 @@ +use std::default; + use egui::{scroll_area::ScrollBarVisibility, vec2, Rect, Vec2}; use crate::{ @@ -20,7 +22,10 @@ struct ScrollState { pub offset: Vec2, pub consumed: Vec2, pub available: Vec2, - pub offset_delta: Vec2 + pub offset_delta: Vec2, + + pub prev_frame_left: bool, + pub prev_frame_right: bool } impl Tabs { @@ -118,7 +123,7 @@ impl Tabs { ui.spacing_mut().item_spacing.x = 0.0; // Tabs have spacing built-in let mut scroll_state: ScrollState = ui.ctx().memory_mut(|m| m.data.get_temp::(id)).unwrap(); - + if scroll_state.consumed.x > scroll_state.available.x { behavior.top_bar_right_ui( &tree.tiles, @@ -132,90 +137,122 @@ impl Tabs { ui.set_clip_rect(ui.available_rect_before_wrap()); // Don't cover the `rtl_ui` buttons. - let total_width = ui.available_width(); - let mut scroll_area_size = Vec2::ZERO; - scroll_area_size.x = total_width - 25.0; - scroll_area_size.y = ui.available_height(); - - let mut area = egui::ScrollArea::horizontal() - .scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden) - .max_width(ui.available_width()-25.0); + println!("{} {}", scroll_state.offset.x, scroll_state.prev_frame_left); - { - // Max is: [`ui.available_width()`] - if scroll_state.offset_delta.x >= (ui.available_width()) { - scroll_state.offset_delta.x = ui.available_width(); - } + let mut consume = ui.available_width(); - if scroll_state.offset_delta.x < 0.0 { - scroll_state.offset_delta.x = 0.0; - } + if scroll_state.offset.x > 20.0 { + // if !scroll_state.prev_frame_left { + // scroll_state.offset_delta.x -= 20.0; + // } - area = area.to_owned().horizontal_scroll_offset(scroll_state.offset.x + scroll_state.offset_delta.x); + scroll_state.prev_frame_left = true; - scroll_state.offset_delta = Vec2::ZERO; + consume -= 20.0; + }else if scroll_state.offset.x > 0.0 { + consume -= scroll_state.offset.x; + }else { + scroll_state.prev_frame_left = false; } - let output = area.show_viewport(ui, |ui, _| { - ui.allocate_ui_with_layout(scroll_area_size, egui::Layout::left_to_right(egui::Align::Center), |ui| { - if !tree.is_root(tile_id) { - // Make the background behind the buttons draggable (to drag the parent container tile): - if ui - .interact( - ui.max_rect(), - ui.id().with("background"), - egui::Sense::drag(), - ) - .on_hover_cursor(egui::CursorIcon::Grab) - .drag_started() - { - ui.memory_mut(|mem| mem.set_dragged_id(tile_id.id())); - } + // let consume = if scroll_state.offset.x > 20.0 || scroll_state.prev_frame_left { + // if !scroll_state.prev_frame_left { + // scroll_state.offset_delta.x -= 20.0; + // scroll_state.prev_frame_left = true; + // } + + // if scroll_state.offset.x == 0.0 { + // scroll_state.prev_frame_left = false; + // } + + // ui.available_width() - 20.0 + // } else { + // scroll_state.prev_frame_left = false; + // ui.available_width() + // }; + + let mut scroll_area_size = Vec2::ZERO; + scroll_area_size.x = consume; + scroll_area_size.y = ui.available_height(); + + ui.allocate_ui_with_layout(scroll_area_size, egui::Layout::left_to_right(egui::Align::Center), | ui | { + let mut area = egui::ScrollArea::horizontal() + .scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden) + .max_width(consume); + + { + // Max is: [`ui.available_width()`] + if scroll_state.offset_delta.x >= ui.available_width() { + scroll_state.offset_delta.x = ui.available_width(); } - for (i, &child_id) in self.children.iter().enumerate() { - let is_being_dragged = is_being_dragged(ui.ctx(), child_id); - - let selected = self.is_active(child_id); - let id = child_id.id(); - - let response = behavior.tab_ui( - &tree.tiles, - ui, - id, - child_id, - selected, - is_being_dragged, - ); - let response = response.on_hover_cursor(egui::CursorIcon::Grab); - if response.clicked() { - next_active = Some(child_id); - response.scroll_to_me(None) - } + area = area.to_owned().horizontal_scroll_offset(scroll_state.offset.x + scroll_state.offset_delta.x); - if let Some(mouse_pos) = drop_context.mouse_pos { - if drop_context.dragged_tile_id.is_some() - && response.rect.contains(mouse_pos) + // Reset delta after use + scroll_state.offset_delta = Vec2::ZERO; + } + + let output = area.show_viewport(ui, |ui, _ | { + ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { + if !tree.is_root(tile_id) { + // Make the background behind the buttons draggable (to drag the parent container tile): + if ui + .interact( + ui.max_rect(), + ui.id().with("background"), + egui::Sense::drag(), + ) + .on_hover_cursor(egui::CursorIcon::Grab) + .drag_started() { - // Expand this tab - maybe the user wants to drop something into it! - next_active = Some(child_id); + ui.memory_mut(|mem| mem.set_dragged_id(tile_id.id())); } } - button_rects.insert(child_id, response.rect); - if is_being_dragged { - dragged_index = Some(i); + for (i, &child_id) in self.children.iter().enumerate() { + let is_being_dragged = is_being_dragged(ui.ctx(), child_id); + + let selected = self.is_active(child_id); + let id = child_id.id(); + + let response = behavior.tab_ui( + &tree.tiles, + ui, + id, + child_id, + selected, + is_being_dragged, + ); + let response = response.on_hover_cursor(egui::CursorIcon::Grab); + if response.clicked() { + next_active = Some(child_id); + response.scroll_to_me(None) + } + + if let Some(mouse_pos) = drop_context.mouse_pos { + if drop_context.dragged_tile_id.is_some() + && response.rect.contains(mouse_pos) + { + // Expand this tab - maybe the user wants to drop something into it! + next_active = Some(child_id); + } + } + + button_rects.insert(child_id, response.rect); + if is_being_dragged { + dragged_index = Some(i); + } } - } + }); }); - }); + scroll_state.offset = output.state.offset; + scroll_state.consumed = output.content_size; + scroll_state.available = output.inner_rect.size(); + }); + if scroll_state.offset.x > 0.0 { - let mut scroll_area_size = Vec2::ZERO; - scroll_area_size.x = 25.0; - scroll_area_size.y = ui.available_height(); - - ui.allocate_ui_with_layout(scroll_area_size, egui::Layout::right_to_left(egui::Align::Center), | ui | { + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), | ui | { behavior.top_bar_left_ui( &tree.tiles, ui, tile_id, self, scroll_state.offset.x, @@ -224,10 +261,6 @@ impl Tabs { }); } - scroll_state.offset = output.state.offset; - scroll_state.consumed = output.content_size; - scroll_state.available = output.inner_rect.size(); - ui.ctx().memory_mut(|m| m.data.insert_temp(id, scroll_state)); }); From c7d10642accf0b3eaaace21336b96c9b177646ae Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 2 Jun 2023 21:54:58 +1000 Subject: [PATCH 10/33] mod: without slide-in --- src/container/tabs.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/container/tabs.rs b/src/container/tabs.rs index 6a1ae5b..22e8de5 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -23,7 +23,7 @@ struct ScrollState { pub consumed: Vec2, pub available: Vec2, pub offset_delta: Vec2, - + pub prev_frame_left: bool, pub prev_frame_right: bool } @@ -137,20 +137,21 @@ impl Tabs { ui.set_clip_rect(ui.available_rect_before_wrap()); // Don't cover the `rtl_ui` buttons. - println!("{} {}", scroll_state.offset.x, scroll_state.prev_frame_left); - let mut consume = ui.available_width(); if scroll_state.offset.x > 20.0 { - // if !scroll_state.prev_frame_left { - // scroll_state.offset_delta.x -= 20.0; - // } + if !scroll_state.prev_frame_left { + scroll_state.offset_delta.x += 20.0; + } scroll_state.prev_frame_left = true; consume -= 20.0; }else if scroll_state.offset.x > 0.0 { - consume -= scroll_state.offset.x; + if scroll_state.prev_frame_left { + scroll_state.offset.x -= 20.0; + } + // consume -= scroll_state.offset.x; }else { scroll_state.prev_frame_left = false; } From bee4b33c915f391f4621dd462560f319140a690a Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 2 Jun 2023 21:57:32 +1000 Subject: [PATCH 11/33] mod: constantification~ --- src/container/tabs.rs | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/src/container/tabs.rs b/src/container/tabs.rs index 22e8de5..4323f4e 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -137,41 +137,29 @@ impl Tabs { ui.set_clip_rect(ui.available_rect_before_wrap()); // Don't cover the `rtl_ui` buttons. + const LEFT_FRAME_SIZE: f32 = 20.0; + let mut consume = ui.available_width(); - if scroll_state.offset.x > 20.0 { + if scroll_state.offset.x > LEFT_FRAME_SIZE { if !scroll_state.prev_frame_left { - scroll_state.offset_delta.x += 20.0; + scroll_state.offset_delta.x += LEFT_FRAME_SIZE; } scroll_state.prev_frame_left = true; - consume -= 20.0; + consume -= LEFT_FRAME_SIZE; }else if scroll_state.offset.x > 0.0 { if scroll_state.prev_frame_left { - scroll_state.offset.x -= 20.0; + scroll_state.offset.x -= LEFT_FRAME_SIZE; } + + // Uncomment the following for an ~animated~ reveal. // consume -= scroll_state.offset.x; }else { scroll_state.prev_frame_left = false; } - // let consume = if scroll_state.offset.x > 20.0 || scroll_state.prev_frame_left { - // if !scroll_state.prev_frame_left { - // scroll_state.offset_delta.x -= 20.0; - // scroll_state.prev_frame_left = true; - // } - - // if scroll_state.offset.x == 0.0 { - // scroll_state.prev_frame_left = false; - // } - - // ui.available_width() - 20.0 - // } else { - // scroll_state.prev_frame_left = false; - // ui.available_width() - // }; - let mut scroll_area_size = Vec2::ZERO; scroll_area_size.x = consume; scroll_area_size.y = ui.available_height(); From c41b7d20af5c8e9c3536f0aed11784e81a9e61ad Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 2 Jun 2023 22:29:46 +1000 Subject: [PATCH 12/33] feat: right hand icon dissapear --- src/container/tabs.rs | 46 +++++++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/src/container/tabs.rs b/src/container/tabs.rs index 4323f4e..8164f1f 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -99,7 +99,7 @@ impl Tabs { ) -> Option { let mut next_active = self.active; - let scroll_state: ScrollState = ScrollState::default(); + let scroll_state: ScrollState = ScrollState { prev_frame_left: false, prev_frame_right: false, ..ScrollState::default() }; let id = ui.make_persistent_id(tile_id); ui.ctx().memory_mut(|m| { @@ -124,23 +124,23 @@ impl Tabs { let mut scroll_state: ScrollState = ui.ctx().memory_mut(|m| m.data.get_temp::(id)).unwrap(); - if scroll_state.consumed.x > scroll_state.available.x { - behavior.top_bar_right_ui( - &tree.tiles, - ui, - tile_id, - self, - scroll_state.offset.x, - &mut scroll_state.offset_delta.x - ); - } - - ui.set_clip_rect(ui.available_rect_before_wrap()); // Don't cover the `rtl_ui` buttons. - const LEFT_FRAME_SIZE: f32 = 20.0; + const RIGHT_FRAME_SIZE: f32 = 20.0; let mut consume = ui.available_width(); + if (scroll_state.offset.x - RIGHT_FRAME_SIZE) > scroll_state.available.x { + if scroll_state.prev_frame_right { + scroll_state.offset_delta.x += RIGHT_FRAME_SIZE; + } + + scroll_state.prev_frame_right = false; + }else if (scroll_state.offset.x - 0.0) > scroll_state.available.x { + // DO NOTHING + }else { + scroll_state.prev_frame_right = true; + } + if scroll_state.offset.x > LEFT_FRAME_SIZE { if !scroll_state.prev_frame_left { scroll_state.offset_delta.x += LEFT_FRAME_SIZE; @@ -160,6 +160,22 @@ impl Tabs { scroll_state.prev_frame_left = false; } + if scroll_state.consumed.x > scroll_state.available.x + && (scroll_state.offset.x - RIGHT_FRAME_SIZE) < scroll_state.available.x { + consume -= RIGHT_FRAME_SIZE; + + behavior.top_bar_right_ui( + &tree.tiles, + ui, + tile_id, + self, + scroll_state.offset.x, + &mut scroll_state.offset_delta.x + ); + } + + ui.set_clip_rect(ui.available_rect_before_wrap()); // Don't cover the `rtl_ui` buttons. + let mut scroll_area_size = Vec2::ZERO; scroll_area_size.x = consume; scroll_area_size.y = ui.available_height(); @@ -240,7 +256,7 @@ impl Tabs { scroll_state.available = output.inner_rect.size(); }); - if scroll_state.offset.x > 0.0 { + if scroll_state.offset.x > LEFT_FRAME_SIZE { ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), | ui | { behavior.top_bar_left_ui( &tree.tiles, ui, tile_id, From ccd488524ab182e0fb19bc82147a4b3653a489ec Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 2 Jun 2023 22:30:18 +1000 Subject: [PATCH 13/33] mod: clippy --- src/container/tabs.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/container/tabs.rs b/src/container/tabs.rs index 8164f1f..f47fc7a 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -1,5 +1,3 @@ -use std::default; - use egui::{scroll_area::ScrollBarVisibility, vec2, Rect, Vec2}; use crate::{ From badf4dc13445bbb8dd1825a0424717ad5c04ab35 Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 14 Jun 2023 20:46:05 +1000 Subject: [PATCH 14/33] mod: late formatting changes :) --- examples/advanced.rs | 8 +- src/container/tabs.rs | 180 +++++++++++++++++++++++------------------- 2 files changed, 103 insertions(+), 85 deletions(-) diff --git a/examples/advanced.rs b/examples/advanced.rs index 19a65d5..9427419 100644 --- a/examples/advanced.rs +++ b/examples/advanced.rs @@ -135,9 +135,9 @@ impl egui_tiles::Behavior for TreeBehavior { _tile_id: egui_tiles::TileId, _tabs: &egui_tiles::Tabs, _offset: f32, - _scroll: &mut f32 + _scroll: &mut f32, ) { - if ui.button("⏴").clicked() { + if ui.button("⏴").clicked() { *_scroll += -45.0; } } @@ -149,13 +149,13 @@ impl egui_tiles::Behavior for TreeBehavior { _tile_id: egui_tiles::TileId, _tabs: &egui_tiles::Tabs, _offset: f32, - _scroll: &mut f32 + _scroll: &mut f32, ) { // if ui.button("➕").clicked() { // self.add_child_to = Some(tile_id); // } - if ui.button("⏵").clicked() { + if ui.button("⏵").clicked() { // Integer value to move scroll by // +'ve is right // -'ve is left diff --git a/src/container/tabs.rs b/src/container/tabs.rs index f47fc7a..37fcd3c 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -23,7 +23,7 @@ struct ScrollState { pub offset_delta: Vec2, pub prev_frame_left: bool, - pub prev_frame_right: bool + pub prev_frame_right: bool, } impl Tabs { @@ -97,7 +97,11 @@ impl Tabs { ) -> Option { let mut next_active = self.active; - let scroll_state: ScrollState = ScrollState { prev_frame_left: false, prev_frame_right: false, ..ScrollState::default() }; + let scroll_state: ScrollState = ScrollState { + prev_frame_left: false, + prev_frame_right: false, + ..ScrollState::default() + }; let id = ui.make_persistent_id(tile_id); ui.ctx().memory_mut(|m| { @@ -120,7 +124,10 @@ impl Tabs { // Add buttons such as "add new tab" ui.spacing_mut().item_spacing.x = 0.0; // Tabs have spacing built-in - let mut scroll_state: ScrollState = ui.ctx().memory_mut(|m| m.data.get_temp::(id)).unwrap(); + let mut scroll_state: ScrollState = ui + .ctx() + .memory_mut(|m| m.data.get_temp::(id)) + .unwrap(); const LEFT_FRAME_SIZE: f32 = 20.0; const RIGHT_FRAME_SIZE: f32 = 20.0; @@ -133,9 +140,9 @@ impl Tabs { } scroll_state.prev_frame_right = false; - }else if (scroll_state.offset.x - 0.0) > scroll_state.available.x { + } else if (scroll_state.offset.x - 0.0) > scroll_state.available.x { // DO NOTHING - }else { + } else { scroll_state.prev_frame_right = true; } @@ -147,19 +154,20 @@ impl Tabs { scroll_state.prev_frame_left = true; consume -= LEFT_FRAME_SIZE; - }else if scroll_state.offset.x > 0.0 { + } else if scroll_state.offset.x > 0.0 { if scroll_state.prev_frame_left { scroll_state.offset.x -= LEFT_FRAME_SIZE; } // Uncomment the following for an ~animated~ reveal. // consume -= scroll_state.offset.x; - }else { + } else { scroll_state.prev_frame_left = false; } - if scroll_state.consumed.x > scroll_state.available.x - && (scroll_state.offset.x - RIGHT_FRAME_SIZE) < scroll_state.available.x { + if scroll_state.consumed.x > scroll_state.available.x + && (scroll_state.offset.x - RIGHT_FRAME_SIZE) < scroll_state.available.x + { consume -= RIGHT_FRAME_SIZE; behavior.top_bar_right_ui( @@ -168,103 +176,113 @@ impl Tabs { tile_id, self, scroll_state.offset.x, - &mut scroll_state.offset_delta.x + &mut scroll_state.offset_delta.x, ); } - ui.set_clip_rect(ui.available_rect_before_wrap()); // Don't cover the `rtl_ui` buttons. + ui.set_clip_rect(ui.available_rect_before_wrap()); // Don't cover the `rtl_ui` buttons. let mut scroll_area_size = Vec2::ZERO; scroll_area_size.x = consume; scroll_area_size.y = ui.available_height(); - ui.allocate_ui_with_layout(scroll_area_size, egui::Layout::left_to_right(egui::Align::Center), | ui | { - let mut area = egui::ScrollArea::horizontal() - .scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden) - .max_width(consume); - - { - // Max is: [`ui.available_width()`] - if scroll_state.offset_delta.x >= ui.available_width() { - scroll_state.offset_delta.x = ui.available_width(); - } + ui.allocate_ui_with_layout( + scroll_area_size, + egui::Layout::left_to_right(egui::Align::Center), + |ui| { + let mut area = egui::ScrollArea::horizontal() + .scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden) + .max_width(consume); + + { + // Max is: [`ui.available_width()`] + if scroll_state.offset_delta.x >= ui.available_width() { + scroll_state.offset_delta.x = ui.available_width(); + } - area = area.to_owned().horizontal_scroll_offset(scroll_state.offset.x + scroll_state.offset_delta.x); + area = area.to_owned().horizontal_scroll_offset( + scroll_state.offset.x + scroll_state.offset_delta.x, + ); - // Reset delta after use - scroll_state.offset_delta = Vec2::ZERO; - } - - let output = area.show_viewport(ui, |ui, _ | { - ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { - if !tree.is_root(tile_id) { - // Make the background behind the buttons draggable (to drag the parent container tile): - if ui - .interact( - ui.max_rect(), - ui.id().with("background"), - egui::Sense::drag(), - ) - .on_hover_cursor(egui::CursorIcon::Grab) - .drag_started() - { - ui.memory_mut(|mem| mem.set_dragged_id(tile_id.id())); - } - } + // Reset delta after use + scroll_state.offset_delta = Vec2::ZERO; + } - for (i, &child_id) in self.children.iter().enumerate() { - let is_being_dragged = is_being_dragged(ui.ctx(), child_id); - - let selected = self.is_active(child_id); - let id = child_id.id(); - - let response = behavior.tab_ui( - &tree.tiles, - ui, - id, - child_id, - selected, - is_being_dragged, - ); - let response = response.on_hover_cursor(egui::CursorIcon::Grab); - if response.clicked() { - next_active = Some(child_id); - response.scroll_to_me(None) + let output = area.show_viewport(ui, |ui, _| { + ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { + if !tree.is_root(tile_id) { + // Make the background behind the buttons draggable (to drag the parent container tile): + if ui + .interact( + ui.max_rect(), + ui.id().with("background"), + egui::Sense::drag(), + ) + .on_hover_cursor(egui::CursorIcon::Grab) + .drag_started() + { + ui.memory_mut(|mem| mem.set_dragged_id(tile_id.id())); + } } - if let Some(mouse_pos) = drop_context.mouse_pos { - if drop_context.dragged_tile_id.is_some() - && response.rect.contains(mouse_pos) - { - // Expand this tab - maybe the user wants to drop something into it! + for (i, &child_id) in self.children.iter().enumerate() { + let is_being_dragged = is_being_dragged(ui.ctx(), child_id); + + let selected = self.is_active(child_id); + let id = child_id.id(); + + let response = behavior.tab_ui( + &tree.tiles, + ui, + id, + child_id, + selected, + is_being_dragged, + ); + let response = response.on_hover_cursor(egui::CursorIcon::Grab); + if response.clicked() { next_active = Some(child_id); + response.scroll_to_me(None) + } + + if let Some(mouse_pos) = drop_context.mouse_pos { + if drop_context.dragged_tile_id.is_some() + && response.rect.contains(mouse_pos) + { + // Expand this tab - maybe the user wants to drop something into it! + next_active = Some(child_id); + } } - } - button_rects.insert(child_id, response.rect); - if is_being_dragged { - dragged_index = Some(i); + button_rects.insert(child_id, response.rect); + if is_being_dragged { + dragged_index = Some(i); + } } - } + }); }); - }); - scroll_state.offset = output.state.offset; - scroll_state.consumed = output.content_size; - scroll_state.available = output.inner_rect.size(); - }); - + scroll_state.offset = output.state.offset; + scroll_state.consumed = output.content_size; + scroll_state.available = output.inner_rect.size(); + }, + ); + if scroll_state.offset.x > LEFT_FRAME_SIZE { - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), | ui | { + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { behavior.top_bar_left_ui( - &tree.tiles, ui, tile_id, - self, scroll_state.offset.x, - &mut scroll_state.offset_delta.x + &tree.tiles, + ui, + tile_id, + self, + scroll_state.offset.x, + &mut scroll_state.offset_delta.x, ); }); } - ui.ctx().memory_mut(|m| m.data.insert_temp(id, scroll_state)); + ui.ctx() + .memory_mut(|m| m.data.insert_temp(id, scroll_state)); }); // ----------- From 1a546889a301f375b04d097b0ff5e86e8b0b948f Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 6 Jul 2023 21:05:37 +1000 Subject: [PATCH 15/33] mod: fixed ui right --- examples/advanced.rs | 27 ++--------------- src/behavior.rs | 18 ++++-------- src/container/tabs.rs | 68 +++++++++++++++++++++++++++---------------- 3 files changed, 51 insertions(+), 62 deletions(-) diff --git a/examples/advanced.rs b/examples/advanced.rs index bd74812..562608f 100644 --- a/examples/advanced.rs +++ b/examples/advanced.rs @@ -128,38 +128,17 @@ impl egui_tiles::Behavior for TreeBehavior { format!("View {}", view.nr).into() } - fn top_bar_left_ui( - &mut self, - _tiles: &egui_tiles::Tiles, - ui: &mut egui::Ui, - _tile_id: egui_tiles::TileId, - _tabs: &egui_tiles::Tabs, - _offset: f32, - _scroll: &mut f32, - ) { - if ui.button("⏴").clicked() { - *_scroll += -45.0; - } - } - fn top_bar_right_ui( &mut self, _tiles: &egui_tiles::Tiles, ui: &mut egui::Ui, - _tile_id: egui_tiles::TileId, + tile_id: egui_tiles::TileId, _tabs: &egui_tiles::Tabs, _offset: f32, _scroll: &mut f32, ) { - // if ui.button("➕").clicked() { - // self.add_child_to = Some(tile_id); - // } - - if ui.button("⏵").clicked() { - // Integer value to move scroll by - // +'ve is right - // -'ve is left - *_scroll += 45.0; + if ui.button("➕").clicked() { + self.add_child_to = Some(tile_id); } } diff --git a/src/behavior.rs b/src/behavior.rs index 86fa58b..bb24a4d 100644 --- a/src/behavior.rs +++ b/src/behavior.rs @@ -108,6 +108,11 @@ pub trait Behavior { /// You can use this to, for instance, add a button for adding new tabs. /// /// The widgets will be added right-to-left. + /// + /// The offset value represents the offset of the right bar UI component. + /// + /// `_scroll` is a mutable reference to the scroll value, adding or subtracting + /// from this value will alter the current horizontal scroll location. fn top_bar_right_ui( &mut self, _tiles: &Tiles, @@ -121,19 +126,6 @@ pub trait Behavior { // } } - fn top_bar_left_ui( - &mut self, - _tiles: &Tiles, - _ui: &mut Ui, - _tile_id: TileId, - _tabs: &crate::Tabs, - _offset: f32, - _scroll: &mut f32, - ) { - // if ui.button("➕").clicked() { - // } - } - /// The height of the bar holding tab titles. fn tab_bar_height(&self, _style: &egui::Style) -> f32 { 24.0 diff --git a/src/container/tabs.rs b/src/container/tabs.rs index c3402c0..f7b0c9f 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -15,14 +15,25 @@ pub struct Tabs { pub active: Option, } -#[derive(Default, Clone)] +/// The current tab scrolling state +#[derive(Default, Debug, Clone)] struct ScrollState { + /// The horizontal (and vertical) offset pub offset: Vec2, + + /// The consumed width and height pub consumed: Vec2, + + /// The available width and height pub available: Vec2, + + /// The current scrolling offset pub offset_delta: Vec2, + /// `true` if the previous frame had the left menu active pub prev_frame_left: bool, + + /// `true` if the previous frame had the right menu active pub prev_frame_right: bool, } @@ -139,14 +150,29 @@ impl Tabs { .memory_mut(|m| m.data.get_temp::(id)) .unwrap(); + // Fixed size icons for `⏴` and `⏵` const LEFT_FRAME_SIZE: f32 = 20.0; const RIGHT_FRAME_SIZE: f32 = 20.0; + // Show Right UI Menu (any size allowed) + behavior.top_bar_right_ui( + &tree.tiles, + ui, + tile_id, + self, + scroll_state.offset.x, + &mut scroll_state.offset_delta.x, + ); + + // Mutable consumable width let mut consume = ui.available_width(); - if (scroll_state.offset.x - RIGHT_FRAME_SIZE) > scroll_state.available.x { + // Determine scroll changes due to left button variability + if scroll_state.consumed.x > scroll_state.available.x + && (scroll_state.offset.x - RIGHT_FRAME_SIZE) > scroll_state.available.x + { if scroll_state.prev_frame_right { - scroll_state.offset_delta.x += RIGHT_FRAME_SIZE; + scroll_state.offset_delta.x -= RIGHT_FRAME_SIZE; } scroll_state.prev_frame_right = false; @@ -156,6 +182,7 @@ impl Tabs { scroll_state.prev_frame_right = true; } + // Determine scroll changes due to right button variability if scroll_state.offset.x > LEFT_FRAME_SIZE { if !scroll_state.prev_frame_left { scroll_state.offset_delta.x += LEFT_FRAME_SIZE; @@ -168,32 +195,28 @@ impl Tabs { if scroll_state.prev_frame_left { scroll_state.offset.x -= LEFT_FRAME_SIZE; } - - // Uncomment the following for an ~animated~ reveal. - // consume -= scroll_state.offset.x; } else { scroll_state.prev_frame_left = false; } - if scroll_state.consumed.x > scroll_state.available.x - && (scroll_state.offset.x - RIGHT_FRAME_SIZE) < scroll_state.available.x + // If consumed > available (by margin), show right scroll icon. + if ((scroll_state.offset.x + scroll_state.available.x) - scroll_state.consumed.x).abs() + >= 1.0 { consume -= RIGHT_FRAME_SIZE; - behavior.top_bar_right_ui( - &tree.tiles, - ui, - tile_id, - self, - scroll_state.offset.x, - &mut scroll_state.offset_delta.x, - ); + if ui.button("⏵").clicked() { + // Integer value to move scroll by + // positive is right + // negative is left + scroll_state.offset_delta.x += 45.0; + } } ui.set_clip_rect(ui.available_rect_before_wrap()); // Don't cover the `rtl_ui` buttons. let mut scroll_area_size = Vec2::ZERO; - scroll_area_size.x = consume; + scroll_area_size.x = consume; // Enforce currently consumable width (left scroll icon not yet placed) scroll_area_size.y = ui.available_height(); ui.allocate_ui_with_layout( @@ -283,14 +306,9 @@ impl Tabs { if scroll_state.offset.x > LEFT_FRAME_SIZE { ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - behavior.top_bar_left_ui( - &tree.tiles, - ui, - tile_id, - self, - scroll_state.offset.x, - &mut scroll_state.offset_delta.x, - ); + if ui.button("⏴").clicked() { + scroll_state.offset_delta.x += -45.0; + } }); } From d859103c51b03955e1475308c86426760e1aa945 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 6 Jul 2023 21:23:13 +1000 Subject: [PATCH 16/33] mod: smoother right button release --- src/container/tabs.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/container/tabs.rs b/src/container/tabs.rs index f7b0c9f..2456b8f 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -164,20 +164,34 @@ impl Tabs { &mut scroll_state.offset_delta.x, ); + println!( + "{:?} {}", + scroll_state, + ((scroll_state.offset.x + scroll_state.available.x) - scroll_state.consumed.x) + .abs() + ); + // Mutable consumable width let mut consume = ui.available_width(); // Determine scroll changes due to left button variability - if scroll_state.consumed.x > scroll_state.available.x - && (scroll_state.offset.x - RIGHT_FRAME_SIZE) > scroll_state.available.x + if ((scroll_state.offset.x + scroll_state.available.x) - scroll_state.consumed.x).abs() + <= 1.0 { if scroll_state.prev_frame_right { - scroll_state.offset_delta.x -= RIGHT_FRAME_SIZE; + scroll_state.offset_delta.x += RIGHT_FRAME_SIZE; } scroll_state.prev_frame_right = false; - } else if (scroll_state.offset.x - 0.0) > scroll_state.available.x { + } else if ((scroll_state.offset.x + RIGHT_FRAME_SIZE + scroll_state.available.x) + - scroll_state.consumed.x) + .abs() + <= 1.0 + { // DO NOTHING + if scroll_state.prev_frame_right { + scroll_state.offset_delta.x += RIGHT_FRAME_SIZE; + } } else { scroll_state.prev_frame_right = true; } From 0dec9d526841927333e23cf27e65a3d12159ff61 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 6 Jul 2023 21:26:57 +1000 Subject: [PATCH 17/33] mod: cleanup --- src/container/tabs.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/container/tabs.rs b/src/container/tabs.rs index 2456b8f..62b87f6 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -118,11 +118,13 @@ impl Tabs { ) -> Option { let mut next_active = self.active; + // Reset current scroll state for the next frame. let scroll_state: ScrollState = ScrollState { prev_frame_left: false, prev_frame_right: false, ..ScrollState::default() }; + let id = ui.make_persistent_id(tile_id); ui.ctx().memory_mut(|m| { @@ -164,20 +166,17 @@ impl Tabs { &mut scroll_state.offset_delta.x, ); - println!( - "{:?} {}", - scroll_state, - ((scroll_state.offset.x + scroll_state.available.x) - scroll_state.consumed.x) - .abs() - ); - // Mutable consumable width let mut consume = ui.available_width(); // Determine scroll changes due to left button variability + // We add the --[------] (used + visible) + // to determine how far has been traveled by the rightmost + // element, and so determines if it can move further forward or not. if ((scroll_state.offset.x + scroll_state.available.x) - scroll_state.consumed.x).abs() <= 1.0 { + // Move to the end to prevent re-caching (infinitely scrolling) if scroll_state.prev_frame_right { scroll_state.offset_delta.x += RIGHT_FRAME_SIZE; } @@ -188,7 +187,7 @@ impl Tabs { .abs() <= 1.0 { - // DO NOTHING + // Alter offset on approach to smooth connection and mitigate jarring motion if scroll_state.prev_frame_right { scroll_state.offset_delta.x += RIGHT_FRAME_SIZE; } From d0941f1fd87fcf710deb2b72309c8ef538bd73d1 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 10 Nov 2023 14:33:05 +0100 Subject: [PATCH 18/33] Fix a couple of clippy linta --- src/container/tabs.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/container/tabs.rs b/src/container/tabs.rs index 62b87f6..8029d7e 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -129,7 +129,7 @@ impl Tabs { ui.ctx().memory_mut(|m| { if m.data.get_temp::(id).is_none() { - m.data.insert_temp(id, scroll_state) + m.data.insert_temp(id, scroll_state); } }); @@ -246,7 +246,7 @@ impl Tabs { scroll_state.offset_delta.x = ui.available_width(); } - area = area.to_owned().horizontal_scroll_offset( + area = area.clone().horizontal_scroll_offset( scroll_state.offset.x + scroll_state.offset_delta.x, ); From 4ac86e30b8533d3b7b6eb7461c6babf521232802 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 10 Nov 2023 14:34:21 +0100 Subject: [PATCH 19/33] Replace `unwrap` with `unwrap_or_default` --- src/container/tabs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/container/tabs.rs b/src/container/tabs.rs index 8029d7e..0d0e0d4 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -150,7 +150,7 @@ impl Tabs { let mut scroll_state: ScrollState = ui .ctx() .memory_mut(|m| m.data.get_temp::(id)) - .unwrap(); + .unwrap_or_default(); // Fixed size icons for `⏴` and `⏵` const LEFT_FRAME_SIZE: f32 = 20.0; From 7757f5e671fbd327032d3a8d283b18fe9be133bf Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 10 Nov 2023 14:40:50 +0100 Subject: [PATCH 20/33] Simplify ScrollState retrieval --- src/container/tabs.rs | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/src/container/tabs.rs b/src/container/tabs.rs index 0d0e0d4..a6b0d0e 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -16,7 +16,7 @@ pub struct Tabs { } /// The current tab scrolling state -#[derive(Default, Debug, Clone)] +#[derive(Clone, Copy, Debug, Default)] struct ScrollState { /// The horizontal (and vertical) offset pub offset: Vec2, @@ -107,6 +107,7 @@ impl Tabs { } /// Returns the next active tab (e.g. the one clicked, or the current). + #[allow(clippy::too_many_lines)] fn tab_bar_ui( &self, tree: &mut Tree, @@ -118,20 +119,11 @@ impl Tabs { ) -> Option { let mut next_active = self.active; - // Reset current scroll state for the next frame. - let scroll_state: ScrollState = ScrollState { - prev_frame_left: false, - prev_frame_right: false, - ..ScrollState::default() - }; - let id = ui.make_persistent_id(tile_id); - ui.ctx().memory_mut(|m| { - if m.data.get_temp::(id).is_none() { - m.data.insert_temp(id, scroll_state); - } - }); + let mut scroll_state = ui + .ctx() + .memory_mut(|m| m.data.get_temp::(id).unwrap_or_default()); let tab_bar_height = behavior.tab_bar_height(ui.style()); let tab_bar_rect = rect.split_top_bottom_at_y(rect.top() + tab_bar_height).0; @@ -147,11 +139,6 @@ impl Tabs { // Add buttons such as "add new tab" ui.spacing_mut().item_spacing.x = 0.0; // Tabs have spacing built-in - let mut scroll_state: ScrollState = ui - .ctx() - .memory_mut(|m| m.data.get_temp::(id)) - .unwrap_or_default(); - // Fixed size icons for `⏴` and `⏵` const LEFT_FRAME_SIZE: f32 = 20.0; const RIGHT_FRAME_SIZE: f32 = 20.0; From 50357e272cb8714a6ef758634adf094ac3b3667f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 10 Nov 2023 14:47:46 +0100 Subject: [PATCH 21/33] Break out some code --- src/container/tabs.rs | 108 ++++++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 52 deletions(-) diff --git a/src/container/tabs.rs b/src/container/tabs.rs index a6b0d0e..446932b 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -5,6 +5,10 @@ use crate::{ TileId, Tiles, Tree, }; +// Fixed size icons for `⏴` and `⏵` +const LEFT_FRAME_SIZE: f32 = 20.0; +const RIGHT_FRAME_SIZE: f32 = 20.0; + /// A container with tabs. Only one tab is open (active) at a time. #[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct Tabs { @@ -37,6 +41,49 @@ struct ScrollState { pub prev_frame_right: bool, } +impl ScrollState { + pub fn update(&mut self, consume: &mut f32) { + // Determine scroll changes due to left button variability + // We add the --[------] (used + visible) + // to determine how far has been traveled by the rightmost + // element, and so determines if it can move further forward or not. + if (self.offset.x + self.available.x - self.consumed.x).abs() <= 1.0 { + // Move to the end to prevent re-caching (infinitely scrolling) + if self.prev_frame_right { + self.offset_delta.x += RIGHT_FRAME_SIZE; + } + + self.prev_frame_right = false; + } else if (self.offset.x + RIGHT_FRAME_SIZE + self.available.x - self.consumed.x).abs() + <= 1.0 + { + // Alter offset on approach to smooth connection and mitigate jarring motion + if self.prev_frame_right { + self.offset_delta.x += RIGHT_FRAME_SIZE; + } + } else { + self.prev_frame_right = true; + } + + // Determine scroll changes due to right button variability + if self.offset.x > LEFT_FRAME_SIZE { + if !self.prev_frame_left { + self.offset_delta.x += LEFT_FRAME_SIZE; + } + + self.prev_frame_left = true; + + *consume -= LEFT_FRAME_SIZE; + } else if self.offset.x > 0.0 { + if self.prev_frame_left { + self.offset.x -= LEFT_FRAME_SIZE; + } + } else { + self.prev_frame_left = false; + } + } +} + impl Tabs { pub fn new(children: Vec) -> Self { let active = children.first().copied(); @@ -119,11 +166,13 @@ impl Tabs { ) -> Option { let mut next_active = self.active; - let id = ui.make_persistent_id(tile_id); + let scroll_state_id = ui.make_persistent_id(tile_id); - let mut scroll_state = ui - .ctx() - .memory_mut(|m| m.data.get_temp::(id).unwrap_or_default()); + let mut scroll_state = ui.ctx().memory_mut(|m| { + m.data + .get_temp::(scroll_state_id) + .unwrap_or_default() + }); let tab_bar_height = behavior.tab_bar_height(ui.style()); let tab_bar_rect = rect.split_top_bottom_at_y(rect.top() + tab_bar_height).0; @@ -139,10 +188,6 @@ impl Tabs { // Add buttons such as "add new tab" ui.spacing_mut().item_spacing.x = 0.0; // Tabs have spacing built-in - // Fixed size icons for `⏴` and `⏵` - const LEFT_FRAME_SIZE: f32 = 20.0; - const RIGHT_FRAME_SIZE: f32 = 20.0; - // Show Right UI Menu (any size allowed) behavior.top_bar_right_ui( &tree.tiles, @@ -156,51 +201,10 @@ impl Tabs { // Mutable consumable width let mut consume = ui.available_width(); - // Determine scroll changes due to left button variability - // We add the --[------] (used + visible) - // to determine how far has been traveled by the rightmost - // element, and so determines if it can move further forward or not. - if ((scroll_state.offset.x + scroll_state.available.x) - scroll_state.consumed.x).abs() - <= 1.0 - { - // Move to the end to prevent re-caching (infinitely scrolling) - if scroll_state.prev_frame_right { - scroll_state.offset_delta.x += RIGHT_FRAME_SIZE; - } - - scroll_state.prev_frame_right = false; - } else if ((scroll_state.offset.x + RIGHT_FRAME_SIZE + scroll_state.available.x) - - scroll_state.consumed.x) - .abs() - <= 1.0 - { - // Alter offset on approach to smooth connection and mitigate jarring motion - if scroll_state.prev_frame_right { - scroll_state.offset_delta.x += RIGHT_FRAME_SIZE; - } - } else { - scroll_state.prev_frame_right = true; - } - - // Determine scroll changes due to right button variability - if scroll_state.offset.x > LEFT_FRAME_SIZE { - if !scroll_state.prev_frame_left { - scroll_state.offset_delta.x += LEFT_FRAME_SIZE; - } - - scroll_state.prev_frame_left = true; - - consume -= LEFT_FRAME_SIZE; - } else if scroll_state.offset.x > 0.0 { - if scroll_state.prev_frame_left { - scroll_state.offset.x -= LEFT_FRAME_SIZE; - } - } else { - scroll_state.prev_frame_left = false; - } + scroll_state.update(&mut consume); // If consumed > available (by margin), show right scroll icon. - if ((scroll_state.offset.x + scroll_state.available.x) - scroll_state.consumed.x).abs() + if (scroll_state.offset.x + scroll_state.available.x - scroll_state.consumed.x).abs() >= 1.0 { consume -= RIGHT_FRAME_SIZE; @@ -313,7 +317,7 @@ impl Tabs { } ui.ctx() - .memory_mut(|m| m.data.insert_temp(id, scroll_state)); + .memory_mut(|m| m.data.insert_temp(scroll_state_id, scroll_state)); }); // ----------- From a5be57c63b4e3385276b10f6031748851fa2405b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 10 Nov 2023 15:59:45 +0100 Subject: [PATCH 22/33] Misc cleanup --- src/container/tabs.rs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/container/tabs.rs b/src/container/tabs.rs index 446932b..f848a7c 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -1,4 +1,4 @@ -use egui::{scroll_area::ScrollBarVisibility, vec2, Rect, Vec2}; +use egui::{scroll_area::ScrollBarVisibility, vec2, NumExt, Rect, Vec2}; use crate::{ is_being_dragged, Behavior, ContainerInsertion, DropContext, InsertionPoint, SimplifyAction, @@ -9,6 +9,9 @@ use crate::{ const LEFT_FRAME_SIZE: f32 = 20.0; const RIGHT_FRAME_SIZE: f32 = 20.0; +/// Clicking the scroll buttons scrolls how much? +const SCROLL_INCREMENT: f32 = 45.0; + /// A container with tabs. Only one tab is open (active) at a time. #[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct Tabs { @@ -22,7 +25,7 @@ pub struct Tabs { /// The current tab scrolling state #[derive(Clone, Copy, Debug, Default)] struct ScrollState { - /// The horizontal (and vertical) offset + /// The current horizontal (and vertical) offset pub offset: Vec2, /// The consumed width and height @@ -31,7 +34,7 @@ struct ScrollState { /// The available width and height pub available: Vec2, - /// The current scrolling offset + /// Change in the scrolling offset to be applied next frame. pub offset_delta: Vec2, /// `true` if the previous frame had the left menu active @@ -43,11 +46,13 @@ struct ScrollState { impl ScrollState { pub fn update(&mut self, consume: &mut f32) { + let eps = 1.0; + // Determine scroll changes due to left button variability // We add the --[------] (used + visible) // to determine how far has been traveled by the rightmost // element, and so determines if it can move further forward or not. - if (self.offset.x + self.available.x - self.consumed.x).abs() <= 1.0 { + if (self.offset.x + self.available.x - self.consumed.x).abs() <= eps { // Move to the end to prevent re-caching (infinitely scrolling) if self.prev_frame_right { self.offset_delta.x += RIGHT_FRAME_SIZE; @@ -55,7 +60,7 @@ impl ScrollState { self.prev_frame_right = false; } else if (self.offset.x + RIGHT_FRAME_SIZE + self.available.x - self.consumed.x).abs() - <= 1.0 + <= eps { // Alter offset on approach to smooth connection and mitigate jarring motion if self.prev_frame_right { @@ -213,7 +218,7 @@ impl Tabs { // Integer value to move scroll by // positive is right // negative is left - scroll_state.offset_delta.x += 45.0; + scroll_state.offset_delta.x += SCROLL_INCREMENT; } } @@ -232,12 +237,10 @@ impl Tabs { .max_width(consume); { - // Max is: [`ui.available_width()`] - if scroll_state.offset_delta.x >= ui.available_width() { - scroll_state.offset_delta.x = ui.available_width(); - } + scroll_state.offset_delta.x = + scroll_state.offset_delta.x.at_most(ui.available_width()); - area = area.clone().horizontal_scroll_offset( + area = area.horizontal_scroll_offset( scroll_state.offset.x + scroll_state.offset_delta.x, ); @@ -311,7 +314,7 @@ impl Tabs { if scroll_state.offset.x > LEFT_FRAME_SIZE { ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { if ui.button("⏴").clicked() { - scroll_state.offset_delta.x += -45.0; + scroll_state.offset_delta.x += -SCROLL_INCREMENT; } }); } From 791740e83b60b734513c1ef0dce84fcd5e0c2808 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 10 Nov 2023 16:01:50 +0100 Subject: [PATCH 23/33] Simplify: remove offset_delta --- examples/advanced.rs | 3 +-- src/behavior.rs | 3 +-- src/container/tabs.rs | 32 ++++++++------------------------ 3 files changed, 10 insertions(+), 28 deletions(-) diff --git a/examples/advanced.rs b/examples/advanced.rs index 562608f..9d1bf65 100644 --- a/examples/advanced.rs +++ b/examples/advanced.rs @@ -134,8 +134,7 @@ impl egui_tiles::Behavior for TreeBehavior { ui: &mut egui::Ui, tile_id: egui_tiles::TileId, _tabs: &egui_tiles::Tabs, - _offset: f32, - _scroll: &mut f32, + _scroll_offset: &mut f32, ) { if ui.button("➕").clicked() { self.add_child_to = Some(tile_id); diff --git a/src/behavior.rs b/src/behavior.rs index bb24a4d..b6544b1 100644 --- a/src/behavior.rs +++ b/src/behavior.rs @@ -119,8 +119,7 @@ pub trait Behavior { _ui: &mut Ui, _tile_id: TileId, _tabs: &crate::Tabs, - _offset: f32, - _scroll: &mut f32, + _scroll_offset: &mut f32, ) { // if ui.button("➕").clicked() { // } diff --git a/src/container/tabs.rs b/src/container/tabs.rs index f848a7c..8121abb 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -34,9 +34,6 @@ struct ScrollState { /// The available width and height pub available: Vec2, - /// Change in the scrolling offset to be applied next frame. - pub offset_delta: Vec2, - /// `true` if the previous frame had the left menu active pub prev_frame_left: bool, @@ -55,7 +52,7 @@ impl ScrollState { if (self.offset.x + self.available.x - self.consumed.x).abs() <= eps { // Move to the end to prevent re-caching (infinitely scrolling) if self.prev_frame_right { - self.offset_delta.x += RIGHT_FRAME_SIZE; + self.offset.x += RIGHT_FRAME_SIZE; } self.prev_frame_right = false; @@ -64,7 +61,7 @@ impl ScrollState { { // Alter offset on approach to smooth connection and mitigate jarring motion if self.prev_frame_right { - self.offset_delta.x += RIGHT_FRAME_SIZE; + self.offset.x += RIGHT_FRAME_SIZE; } } else { self.prev_frame_right = true; @@ -73,7 +70,7 @@ impl ScrollState { // Determine scroll changes due to right button variability if self.offset.x > LEFT_FRAME_SIZE { if !self.prev_frame_left { - self.offset_delta.x += LEFT_FRAME_SIZE; + self.offset.x += LEFT_FRAME_SIZE; } self.prev_frame_left = true; @@ -194,14 +191,7 @@ impl Tabs { ui.spacing_mut().item_spacing.x = 0.0; // Tabs have spacing built-in // Show Right UI Menu (any size allowed) - behavior.top_bar_right_ui( - &tree.tiles, - ui, - tile_id, - self, - scroll_state.offset.x, - &mut scroll_state.offset_delta.x, - ); + behavior.top_bar_right_ui(&tree.tiles, ui, tile_id, self, &mut scroll_state.offset.x); // Mutable consumable width let mut consume = ui.available_width(); @@ -218,7 +208,7 @@ impl Tabs { // Integer value to move scroll by // positive is right // negative is left - scroll_state.offset_delta.x += SCROLL_INCREMENT; + scroll_state.offset.x += SCROLL_INCREMENT; } } @@ -237,15 +227,9 @@ impl Tabs { .max_width(consume); { - scroll_state.offset_delta.x = - scroll_state.offset_delta.x.at_most(ui.available_width()); - - area = area.horizontal_scroll_offset( - scroll_state.offset.x + scroll_state.offset_delta.x, - ); + scroll_state.offset.x = scroll_state.offset.x.at_most(ui.available_width()); - // Reset delta after use - scroll_state.offset_delta = Vec2::ZERO; + area = area.horizontal_scroll_offset(scroll_state.offset.x); } let output = area.show_viewport(ui, |ui, _| { @@ -314,7 +298,7 @@ impl Tabs { if scroll_state.offset.x > LEFT_FRAME_SIZE { ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { if ui.button("⏴").clicked() { - scroll_state.offset_delta.x += -SCROLL_INCREMENT; + scroll_state.offset.x += -SCROLL_INCREMENT; } }); } From 17a3a9f0e2c94150a3b7480800c2ba96988a5794 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 10 Nov 2023 16:13:53 +0100 Subject: [PATCH 24/33] Better naming --- src/container/tabs.rs | 46 ++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/src/container/tabs.rs b/src/container/tabs.rs index 8121abb..6ea1050 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -28,10 +28,10 @@ struct ScrollState { /// The current horizontal (and vertical) offset pub offset: Vec2, - /// The consumed width and height - pub consumed: Vec2, + /// The size of all the buttons last frame. + pub content_size: Vec2, - /// The available width and height + /// The available size for the buttons. pub available: Vec2, /// `true` if the previous frame had the left menu active @@ -42,21 +42,21 @@ struct ScrollState { } impl ScrollState { - pub fn update(&mut self, consume: &mut f32) { + pub fn update(&mut self, scroll_area_width: &mut f32) { let eps = 1.0; // Determine scroll changes due to left button variability // We add the --[------] (used + visible) // to determine how far has been traveled by the rightmost // element, and so determines if it can move further forward or not. - if (self.offset.x + self.available.x - self.consumed.x).abs() <= eps { + if (self.offset.x + self.available.x - self.content_size.x).abs() <= eps { // Move to the end to prevent re-caching (infinitely scrolling) if self.prev_frame_right { self.offset.x += RIGHT_FRAME_SIZE; } self.prev_frame_right = false; - } else if (self.offset.x + RIGHT_FRAME_SIZE + self.available.x - self.consumed.x).abs() + } else if (self.offset.x + RIGHT_FRAME_SIZE + self.available.x - self.content_size.x).abs() <= eps { // Alter offset on approach to smooth connection and mitigate jarring motion @@ -75,7 +75,7 @@ impl ScrollState { self.prev_frame_left = true; - *consume -= LEFT_FRAME_SIZE; + *scroll_area_width -= LEFT_FRAME_SIZE; } else if self.offset.x > 0.0 { if self.prev_frame_left { self.offset.x -= LEFT_FRAME_SIZE; @@ -193,16 +193,17 @@ impl Tabs { // Show Right UI Menu (any size allowed) behavior.top_bar_right_ui(&tree.tiles, ui, tile_id, self, &mut scroll_state.offset.x); - // Mutable consumable width - let mut consume = ui.available_width(); + // We will subtract the button widths from this. + let mut scroll_area_width = ui.available_width(); - scroll_state.update(&mut consume); + scroll_state.update(&mut scroll_area_width); // If consumed > available (by margin), show right scroll icon. - if (scroll_state.offset.x + scroll_state.available.x - scroll_state.consumed.x).abs() + if (scroll_state.offset.x + scroll_state.available.x - scroll_state.content_size.x) + .abs() >= 1.0 { - consume -= RIGHT_FRAME_SIZE; + scroll_area_width -= RIGHT_FRAME_SIZE; if ui.button("⏵").clicked() { // Integer value to move scroll by @@ -214,25 +215,20 @@ impl Tabs { ui.set_clip_rect(ui.available_rect_before_wrap()); // Don't cover the `rtl_ui` buttons. - let mut scroll_area_size = Vec2::ZERO; - scroll_area_size.x = consume; // Enforce currently consumable width (left scroll icon not yet placed) - scroll_area_size.y = ui.available_height(); + let scroll_area_size = vec2(scroll_area_width, ui.available_height()); ui.allocate_ui_with_layout( scroll_area_size, egui::Layout::left_to_right(egui::Align::Center), |ui| { - let mut area = egui::ScrollArea::horizontal() - .scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden) - .max_width(consume); - - { - scroll_state.offset.x = scroll_state.offset.x.at_most(ui.available_width()); + scroll_state.offset.x = scroll_state.offset.x.at_most(ui.available_width()); - area = area.horizontal_scroll_offset(scroll_state.offset.x); - } + let scroll_area = egui::ScrollArea::horizontal() + .scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden) + .max_width(scroll_area_width) + .horizontal_scroll_offset(scroll_state.offset.x); - let output = area.show_viewport(ui, |ui, _| { + let output = scroll_area.show_viewport(ui, |ui, _| { ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { if !tree.is_root(tile_id) { // Make the background behind the buttons draggable (to drag the parent container tile): @@ -290,7 +286,7 @@ impl Tabs { }); scroll_state.offset = output.state.offset; - scroll_state.consumed = output.content_size; + scroll_state.content_size = output.content_size; scroll_state.available = output.inner_rect.size(); }, ); From c3f5527535ae6c34bbe67d1db2edd2c48461f0ac Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 10 Nov 2023 17:03:34 +0100 Subject: [PATCH 25/33] Some better naming --- src/container/tabs.rs | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/container/tabs.rs b/src/container/tabs.rs index 6ea1050..26e5518 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -6,8 +6,8 @@ use crate::{ }; // Fixed size icons for `⏴` and `⏵` -const LEFT_FRAME_SIZE: f32 = 20.0; -const RIGHT_FRAME_SIZE: f32 = 20.0; +const LEFT_SCROLL_ARROW_SIZE: f32 = 20.0; +const RIGHT_SCROLL_ARROW_SIZE: f32 = 20.0; /// Clicking the scroll buttons scrolls how much? const SCROLL_INCREMENT: f32 = 45.0; @@ -25,19 +25,22 @@ pub struct Tabs { /// The current tab scrolling state #[derive(Clone, Copy, Debug, Default)] struct ScrollState { - /// The current horizontal (and vertical) offset + /// The current horizontal (and vertical) offset. + /// + /// Positive: scroll right. + /// Negatie: scroll left. pub offset: Vec2, - /// The size of all the buttons last frame. + /// The size of all the tabs last frame. pub content_size: Vec2, - /// The available size for the buttons. + /// The available size for the tabs. pub available: Vec2, - /// `true` if the previous frame had the left menu active + /// Did we show the left scroll-arrow last frame? pub prev_frame_left: bool, - /// `true` if the previous frame had the right menu active + /// Did we show the right scroll-arrow last frame? pub prev_frame_right: bool, } @@ -52,33 +55,34 @@ impl ScrollState { if (self.offset.x + self.available.x - self.content_size.x).abs() <= eps { // Move to the end to prevent re-caching (infinitely scrolling) if self.prev_frame_right { - self.offset.x += RIGHT_FRAME_SIZE; + self.offset.x += RIGHT_SCROLL_ARROW_SIZE; } self.prev_frame_right = false; - } else if (self.offset.x + RIGHT_FRAME_SIZE + self.available.x - self.content_size.x).abs() + } else if (self.offset.x + RIGHT_SCROLL_ARROW_SIZE + self.available.x - self.content_size.x) + .abs() <= eps { // Alter offset on approach to smooth connection and mitigate jarring motion if self.prev_frame_right { - self.offset.x += RIGHT_FRAME_SIZE; + self.offset.x += RIGHT_SCROLL_ARROW_SIZE; } } else { self.prev_frame_right = true; } // Determine scroll changes due to right button variability - if self.offset.x > LEFT_FRAME_SIZE { + if self.offset.x > LEFT_SCROLL_ARROW_SIZE { if !self.prev_frame_left { - self.offset.x += LEFT_FRAME_SIZE; + self.offset.x += LEFT_SCROLL_ARROW_SIZE; } self.prev_frame_left = true; - *scroll_area_width -= LEFT_FRAME_SIZE; + *scroll_area_width -= LEFT_SCROLL_ARROW_SIZE; } else if self.offset.x > 0.0 { if self.prev_frame_left { - self.offset.x -= LEFT_FRAME_SIZE; + self.offset.x -= LEFT_SCROLL_ARROW_SIZE; } } else { self.prev_frame_left = false; @@ -203,12 +207,9 @@ impl Tabs { .abs() >= 1.0 { - scroll_area_width -= RIGHT_FRAME_SIZE; + scroll_area_width -= RIGHT_SCROLL_ARROW_SIZE; if ui.button("⏵").clicked() { - // Integer value to move scroll by - // positive is right - // negative is left scroll_state.offset.x += SCROLL_INCREMENT; } } @@ -291,7 +292,7 @@ impl Tabs { }, ); - if scroll_state.offset.x > LEFT_FRAME_SIZE { + if scroll_state.offset.x > LEFT_SCROLL_ARROW_SIZE { ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { if ui.button("⏴").clicked() { scroll_state.offset.x += -SCROLL_INCREMENT; From 9e65eaa8158355c8b91b63a1e6458a8be5241ea7 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 10 Nov 2023 17:07:35 +0100 Subject: [PATCH 26/33] Better naming --- src/container/tabs.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/container/tabs.rs b/src/container/tabs.rs index 26e5518..5e4dc7e 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -38,10 +38,10 @@ struct ScrollState { pub available: Vec2, /// Did we show the left scroll-arrow last frame? - pub prev_frame_left: bool, + pub showed_left_arrow_prev: bool, /// Did we show the right scroll-arrow last frame? - pub prev_frame_right: bool, + pub showed_right_arrow_prev: bool, } impl ScrollState { @@ -54,38 +54,38 @@ impl ScrollState { // element, and so determines if it can move further forward or not. if (self.offset.x + self.available.x - self.content_size.x).abs() <= eps { // Move to the end to prevent re-caching (infinitely scrolling) - if self.prev_frame_right { + if self.showed_right_arrow_prev { self.offset.x += RIGHT_SCROLL_ARROW_SIZE; } - self.prev_frame_right = false; + self.showed_right_arrow_prev = false; } else if (self.offset.x + RIGHT_SCROLL_ARROW_SIZE + self.available.x - self.content_size.x) .abs() <= eps { // Alter offset on approach to smooth connection and mitigate jarring motion - if self.prev_frame_right { + if self.showed_right_arrow_prev { self.offset.x += RIGHT_SCROLL_ARROW_SIZE; } } else { - self.prev_frame_right = true; + self.showed_right_arrow_prev = true; } // Determine scroll changes due to right button variability if self.offset.x > LEFT_SCROLL_ARROW_SIZE { - if !self.prev_frame_left { + if !self.showed_left_arrow_prev { self.offset.x += LEFT_SCROLL_ARROW_SIZE; } - self.prev_frame_left = true; + self.showed_left_arrow_prev = true; *scroll_area_width -= LEFT_SCROLL_ARROW_SIZE; } else if self.offset.x > 0.0 { - if self.prev_frame_left { + if self.showed_left_arrow_prev { self.offset.x -= LEFT_SCROLL_ARROW_SIZE; } } else { - self.prev_frame_left = false; + self.showed_left_arrow_prev = false; } } } From e80a794e8aaab15885f8e60d69426bfd6bb49dcd Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 10 Nov 2023 17:11:09 +0100 Subject: [PATCH 27/33] Combine the scroll sizes --- src/container/tabs.rs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/container/tabs.rs b/src/container/tabs.rs index 5e4dc7e..92ee344 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -5,9 +5,8 @@ use crate::{ TileId, Tiles, Tree, }; -// Fixed size icons for `⏴` and `⏵` -const LEFT_SCROLL_ARROW_SIZE: f32 = 20.0; -const RIGHT_SCROLL_ARROW_SIZE: f32 = 20.0; +/// Fixed size icons for `⏴` and `⏵` +const SCROLL_ARROW_SIZE: f32 = 20.0; /// Clicking the scroll buttons scrolls how much? const SCROLL_INCREMENT: f32 = 45.0; @@ -55,34 +54,33 @@ impl ScrollState { if (self.offset.x + self.available.x - self.content_size.x).abs() <= eps { // Move to the end to prevent re-caching (infinitely scrolling) if self.showed_right_arrow_prev { - self.offset.x += RIGHT_SCROLL_ARROW_SIZE; + self.offset.x += SCROLL_ARROW_SIZE; } self.showed_right_arrow_prev = false; - } else if (self.offset.x + RIGHT_SCROLL_ARROW_SIZE + self.available.x - self.content_size.x) - .abs() + } else if (self.offset.x + SCROLL_ARROW_SIZE + self.available.x - self.content_size.x).abs() <= eps { // Alter offset on approach to smooth connection and mitigate jarring motion if self.showed_right_arrow_prev { - self.offset.x += RIGHT_SCROLL_ARROW_SIZE; + self.offset.x += SCROLL_ARROW_SIZE; } } else { self.showed_right_arrow_prev = true; } // Determine scroll changes due to right button variability - if self.offset.x > LEFT_SCROLL_ARROW_SIZE { + if self.offset.x > SCROLL_ARROW_SIZE { if !self.showed_left_arrow_prev { - self.offset.x += LEFT_SCROLL_ARROW_SIZE; + self.offset.x += SCROLL_ARROW_SIZE; } self.showed_left_arrow_prev = true; - *scroll_area_width -= LEFT_SCROLL_ARROW_SIZE; + *scroll_area_width -= SCROLL_ARROW_SIZE; } else if self.offset.x > 0.0 { if self.showed_left_arrow_prev { - self.offset.x -= LEFT_SCROLL_ARROW_SIZE; + self.offset.x -= SCROLL_ARROW_SIZE; } } else { self.showed_left_arrow_prev = false; @@ -207,7 +205,7 @@ impl Tabs { .abs() >= 1.0 { - scroll_area_width -= RIGHT_SCROLL_ARROW_SIZE; + scroll_area_width -= SCROLL_ARROW_SIZE; if ui.button("⏵").clicked() { scroll_state.offset.x += SCROLL_INCREMENT; @@ -292,7 +290,7 @@ impl Tabs { }, ); - if scroll_state.offset.x > LEFT_SCROLL_ARROW_SIZE { + if scroll_state.offset.x > SCROLL_ARROW_SIZE { ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { if ui.button("⏴").clicked() { scroll_state.offset.x += -SCROLL_INCREMENT; From 68abe0cc1ad2c241b0c404f0bdf72af9fea3825f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 10 Nov 2023 17:33:21 +0100 Subject: [PATCH 28/33] Simplify the scroll logic --- src/container/tabs.rs | 87 +++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 52 deletions(-) diff --git a/src/container/tabs.rs b/src/container/tabs.rs index 92ee344..63e567d 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -36,6 +36,12 @@ struct ScrollState { /// The available size for the tabs. pub available: Vec2, + /// Show the left scroll-arrow this frame? + pub show_left_arrow: bool, + + /// Show the right scroll-arrow this frame? + pub show_right_arrow: bool, + /// Did we show the left scroll-arrow last frame? pub showed_left_arrow_prev: bool, @@ -45,46 +51,26 @@ struct ScrollState { impl ScrollState { pub fn update(&mut self, scroll_area_width: &mut f32) { - let eps = 1.0; - - // Determine scroll changes due to left button variability - // We add the --[------] (used + visible) - // to determine how far has been traveled by the rightmost - // element, and so determines if it can move further forward or not. - if (self.offset.x + self.available.x - self.content_size.x).abs() <= eps { - // Move to the end to prevent re-caching (infinitely scrolling) - if self.showed_right_arrow_prev { - self.offset.x += SCROLL_ARROW_SIZE; - } + let margin = 1.0; - self.showed_right_arrow_prev = false; - } else if (self.offset.x + SCROLL_ARROW_SIZE + self.available.x - self.content_size.x).abs() - <= eps - { - // Alter offset on approach to smooth connection and mitigate jarring motion - if self.showed_right_arrow_prev { - self.offset.x += SCROLL_ARROW_SIZE; - } - } else { - self.showed_right_arrow_prev = true; + self.show_left_arrow = SCROLL_ARROW_SIZE < self.offset.x; + + if self.show_left_arrow { + *scroll_area_width -= SCROLL_ARROW_SIZE; } - // Determine scroll changes due to right button variability - if self.offset.x > SCROLL_ARROW_SIZE { - if !self.showed_left_arrow_prev { - self.offset.x += SCROLL_ARROW_SIZE; - } + self.show_right_arrow = self.offset.x + *scroll_area_width + margin < self.content_size.x; - self.showed_left_arrow_prev = true; + // Compensate for showing/hiding of arrow: + self.offset.x += SCROLL_ARROW_SIZE + * ((self.show_left_arrow as i32 as f32) - (self.showed_left_arrow_prev as i32 as f32)); + if self.show_right_arrow { *scroll_area_width -= SCROLL_ARROW_SIZE; - } else if self.offset.x > 0.0 { - if self.showed_left_arrow_prev { - self.offset.x -= SCROLL_ARROW_SIZE; - } - } else { - self.showed_left_arrow_prev = false; } + + self.showed_left_arrow_prev = self.show_left_arrow; + self.showed_right_arrow_prev = self.show_right_arrow; } } @@ -170,14 +156,6 @@ impl Tabs { ) -> Option { let mut next_active = self.active; - let scroll_state_id = ui.make_persistent_id(tile_id); - - let mut scroll_state = ui.ctx().memory_mut(|m| { - m.data - .get_temp::(scroll_state_id) - .unwrap_or_default() - }); - let tab_bar_height = behavior.tab_bar_height(ui.style()); let tab_bar_rect = rect.split_top_bottom_at_y(rect.top() + tab_bar_height).0; let mut ui = ui.child_ui(tab_bar_rect, *ui.layout()); @@ -192,6 +170,13 @@ impl Tabs { // Add buttons such as "add new tab" ui.spacing_mut().item_spacing.x = 0.0; // Tabs have spacing built-in + let scroll_state_id = ui.make_persistent_id(tile_id); + let mut scroll_state = ui.ctx().memory_mut(|m| { + m.data + .get_temp::(scroll_state_id) + .unwrap_or_default() + }); + // Show Right UI Menu (any size allowed) behavior.top_bar_right_ui(&tree.tiles, ui, tile_id, self, &mut scroll_state.offset.x); @@ -201,15 +186,8 @@ impl Tabs { scroll_state.update(&mut scroll_area_width); // If consumed > available (by margin), show right scroll icon. - if (scroll_state.offset.x + scroll_state.available.x - scroll_state.content_size.x) - .abs() - >= 1.0 - { - scroll_area_width -= SCROLL_ARROW_SIZE; - - if ui.button("⏵").clicked() { - scroll_state.offset.x += SCROLL_INCREMENT; - } + if scroll_state.show_right_arrow && ui.button("⏵").clicked() { + scroll_state.offset.x += SCROLL_INCREMENT; } ui.set_clip_rect(ui.available_rect_before_wrap()); // Don't cover the `rtl_ui` buttons. @@ -220,11 +198,16 @@ impl Tabs { scroll_area_size, egui::Layout::left_to_right(egui::Align::Center), |ui| { - scroll_state.offset.x = scroll_state.offset.x.at_most(ui.available_width()); + scroll_state.offset.x = scroll_state + .offset + .x + .at_most(scroll_state.content_size.x - ui.available_width()); + scroll_state.offset.x = scroll_state.offset.x.at_least(0.0); let scroll_area = egui::ScrollArea::horizontal() .scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden) .max_width(scroll_area_width) + .auto_shrink([false; 2]) .horizontal_scroll_offset(scroll_state.offset.x); let output = scroll_area.show_viewport(ui, |ui, _| { @@ -290,7 +273,7 @@ impl Tabs { }, ); - if scroll_state.offset.x > SCROLL_ARROW_SIZE { + if scroll_state.show_left_arrow { ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { if ui.button("⏴").clicked() { scroll_state.offset.x += -SCROLL_INCREMENT; From 7315c2d7a8cbdd3af6c5959e23d62d22927a916b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 10 Nov 2023 17:37:19 +0100 Subject: [PATCH 29/33] Single-dimension scrolling --- src/container/tabs.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/container/tabs.rs b/src/container/tabs.rs index 63e567d..901e10f 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -24,11 +24,11 @@ pub struct Tabs { /// The current tab scrolling state #[derive(Clone, Copy, Debug, Default)] struct ScrollState { - /// The current horizontal (and vertical) offset. + /// The current horizontal scroll offset. /// /// Positive: scroll right. /// Negatie: scroll left. - pub offset: Vec2, + pub offset: f32, /// The size of all the tabs last frame. pub content_size: Vec2, @@ -53,16 +53,16 @@ impl ScrollState { pub fn update(&mut self, scroll_area_width: &mut f32) { let margin = 1.0; - self.show_left_arrow = SCROLL_ARROW_SIZE < self.offset.x; + self.show_left_arrow = SCROLL_ARROW_SIZE < self.offset; if self.show_left_arrow { *scroll_area_width -= SCROLL_ARROW_SIZE; } - self.show_right_arrow = self.offset.x + *scroll_area_width + margin < self.content_size.x; + self.show_right_arrow = self.offset + *scroll_area_width + margin < self.content_size.x; // Compensate for showing/hiding of arrow: - self.offset.x += SCROLL_ARROW_SIZE + self.offset += SCROLL_ARROW_SIZE * ((self.show_left_arrow as i32 as f32) - (self.showed_left_arrow_prev as i32 as f32)); if self.show_right_arrow { @@ -178,7 +178,7 @@ impl Tabs { }); // Show Right UI Menu (any size allowed) - behavior.top_bar_right_ui(&tree.tiles, ui, tile_id, self, &mut scroll_state.offset.x); + behavior.top_bar_right_ui(&tree.tiles, ui, tile_id, self, &mut scroll_state.offset); // We will subtract the button widths from this. let mut scroll_area_width = ui.available_width(); @@ -187,7 +187,7 @@ impl Tabs { // If consumed > available (by margin), show right scroll icon. if scroll_state.show_right_arrow && ui.button("⏵").clicked() { - scroll_state.offset.x += SCROLL_INCREMENT; + scroll_state.offset += SCROLL_INCREMENT; } ui.set_clip_rect(ui.available_rect_before_wrap()); // Don't cover the `rtl_ui` buttons. @@ -198,17 +198,16 @@ impl Tabs { scroll_area_size, egui::Layout::left_to_right(egui::Align::Center), |ui| { - scroll_state.offset.x = scroll_state + scroll_state.offset = scroll_state .offset - .x .at_most(scroll_state.content_size.x - ui.available_width()); - scroll_state.offset.x = scroll_state.offset.x.at_least(0.0); + scroll_state.offset = scroll_state.offset.at_least(0.0); let scroll_area = egui::ScrollArea::horizontal() .scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden) .max_width(scroll_area_width) .auto_shrink([false; 2]) - .horizontal_scroll_offset(scroll_state.offset.x); + .horizontal_scroll_offset(scroll_state.offset); let output = scroll_area.show_viewport(ui, |ui, _| { ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { @@ -267,7 +266,7 @@ impl Tabs { }); }); - scroll_state.offset = output.state.offset; + scroll_state.offset = output.state.offset.x; scroll_state.content_size = output.content_size; scroll_state.available = output.inner_rect.size(); }, @@ -276,7 +275,7 @@ impl Tabs { if scroll_state.show_left_arrow { ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { if ui.button("⏴").clicked() { - scroll_state.offset.x += -SCROLL_INCREMENT; + scroll_state.offset += -SCROLL_INCREMENT; } }); } From 20c2a9dcfb6039385ea431a3022430fb5e9b6c76 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 10 Nov 2023 19:55:32 +0100 Subject: [PATCH 30/33] Animate the scrolling when you click the buttons --- src/container/tabs.rs | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/container/tabs.rs b/src/container/tabs.rs index 901e10f..52d64d3 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -9,7 +9,7 @@ use crate::{ const SCROLL_ARROW_SIZE: f32 = 20.0; /// Clicking the scroll buttons scrolls how much? -const SCROLL_INCREMENT: f32 = 45.0; +const SCROLL_INCREMENT: f32 = 100.0; // TODO: scroll based on availbale width, e.g. 1/3 of that /// A container with tabs. Only one tab is open (active) at a time. #[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] @@ -30,6 +30,10 @@ struct ScrollState { /// Negatie: scroll left. pub offset: f32, + /// Outstanding offset to apply smoothly over the next few frames. + /// This is what the buttons update. + pub offset_debt: f32, + /// The size of all the tabs last frame. pub content_size: Vec2, @@ -50,8 +54,8 @@ struct ScrollState { } impl ScrollState { - pub fn update(&mut self, scroll_area_width: &mut f32) { - let margin = 1.0; + pub fn update(&mut self, ctx: &egui::Context, scroll_area_width: &mut f32) { + let margin = 0.1; self.show_left_arrow = SCROLL_ARROW_SIZE < self.offset; @@ -71,6 +75,22 @@ impl ScrollState { self.showed_left_arrow_prev = self.show_left_arrow; self.showed_right_arrow_prev = self.show_right_arrow; + + if self.offset_debt != 0.0 { + const SPEED: f32 = 500.0; + + let dt = ctx.input(|i| i.stable_dt).min(0.1); + let max_movement = dt * SPEED; + if self.offset_debt.abs() <= max_movement { + self.offset += self.offset_debt; + self.offset_debt = 0.0; + } else { + let movement = self.offset_debt.signum() * max_movement; + self.offset += movement; + self.offset_debt -= movement; + ctx.request_repaint(); + } + } } } @@ -183,11 +203,11 @@ impl Tabs { // We will subtract the button widths from this. let mut scroll_area_width = ui.available_width(); - scroll_state.update(&mut scroll_area_width); + scroll_state.update(ui.ctx(), &mut scroll_area_width); // If consumed > available (by margin), show right scroll icon. if scroll_state.show_right_arrow && ui.button("⏵").clicked() { - scroll_state.offset += SCROLL_INCREMENT; + scroll_state.offset_debt += SCROLL_INCREMENT; } ui.set_clip_rect(ui.available_rect_before_wrap()); // Don't cover the `rtl_ui` buttons. @@ -275,7 +295,7 @@ impl Tabs { if scroll_state.show_left_arrow { ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { if ui.button("⏴").clicked() { - scroll_state.offset += -SCROLL_INCREMENT; + scroll_state.offset_debt -= SCROLL_INCREMENT; } }); } From dab6b6d1adb96d809c4e47287967ec20e6410ddc Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 10 Nov 2023 20:46:07 +0100 Subject: [PATCH 31/33] Simpler and better layout calculations --- src/behavior.rs | 6 +- src/container/tabs.rs | 180 +++++++++++++++++++++++------------------- 2 files changed, 100 insertions(+), 86 deletions(-) diff --git a/src/behavior.rs b/src/behavior.rs index b6544b1..6fd37e1 100644 --- a/src/behavior.rs +++ b/src/behavior.rs @@ -109,10 +109,8 @@ pub trait Behavior { /// /// The widgets will be added right-to-left. /// - /// The offset value represents the offset of the right bar UI component. - /// - /// `_scroll` is a mutable reference to the scroll value, adding or subtracting - /// from this value will alter the current horizontal scroll location. + /// `_scroll_offset` is a mutable reference to the tab scroll value. + /// Adding to this value will scroll the tabs to the right, subtracting to the left. fn top_bar_right_ui( &mut self, _tiles: &Tiles, diff --git a/src/container/tabs.rs b/src/container/tabs.rs index 52d64d3..b43521a 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -6,10 +6,7 @@ use crate::{ }; /// Fixed size icons for `⏴` and `⏵` -const SCROLL_ARROW_SIZE: f32 = 20.0; - -/// Clicking the scroll buttons scrolls how much? -const SCROLL_INCREMENT: f32 = 100.0; // TODO: scroll based on availbale width, e.g. 1/3 of that +const SCROLL_ARROW_SIZE: Vec2 = Vec2::splat(20.0); /// A container with tabs. Only one tab is open (active) at a time. #[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] @@ -54,23 +51,25 @@ struct ScrollState { } impl ScrollState { - pub fn update(&mut self, ctx: &egui::Context, scroll_area_width: &mut f32) { + pub fn update(&mut self, ui: &egui::Ui, scroll_area_width: &mut f32) { + let button_and_spacing_width = SCROLL_ARROW_SIZE.x + ui.spacing().item_spacing.x; + let margin = 0.1; - self.show_left_arrow = SCROLL_ARROW_SIZE < self.offset; + self.show_left_arrow = SCROLL_ARROW_SIZE.x < self.offset; if self.show_left_arrow { - *scroll_area_width -= SCROLL_ARROW_SIZE; + *scroll_area_width -= button_and_spacing_width; } self.show_right_arrow = self.offset + *scroll_area_width + margin < self.content_size.x; // Compensate for showing/hiding of arrow: - self.offset += SCROLL_ARROW_SIZE + self.offset += button_and_spacing_width * ((self.show_left_arrow as i32 as f32) - (self.showed_left_arrow_prev as i32 as f32)); if self.show_right_arrow { - *scroll_area_width -= SCROLL_ARROW_SIZE; + *scroll_area_width -= button_and_spacing_width; } self.showed_left_arrow_prev = self.show_left_arrow; @@ -79,7 +78,7 @@ impl ScrollState { if self.offset_debt != 0.0 { const SPEED: f32 = 500.0; - let dt = ctx.input(|i| i.stable_dt).min(0.1); + let dt = ui.input(|i| i.stable_dt).min(0.1); let max_movement = dt * SPEED; if self.offset_debt.abs() <= max_movement { self.offset += self.offset_debt; @@ -88,10 +87,40 @@ impl ScrollState { let movement = self.offset_debt.signum() * max_movement; self.offset += movement; self.offset_debt -= movement; - ctx.request_repaint(); + ui.ctx().request_repaint(); } } } + + fn scroll_increment(&self) -> f32 { + (self.available.x / 3.0).at_least(20.0) + } + + pub fn left_arrow(&mut self, ui: &mut egui::Ui) { + if !self.show_left_arrow { + return; + } + + if ui + .add_sized(SCROLL_ARROW_SIZE, egui::Button::new("⏴")) + .clicked() + { + self.offset_debt -= self.scroll_increment(); + } + } + + pub fn right_arrow(&mut self, ui: &mut egui::Ui) { + if !self.show_right_arrow { + return; + } + + if ui + .add_sized(SCROLL_ARROW_SIZE, egui::Button::new("⏵")) + .clicked() + { + self.offset_debt += self.scroll_increment(); + } + } } impl Tabs { @@ -187,9 +216,6 @@ impl Tabs { .rect_filled(ui.max_rect(), 0.0, behavior.tab_bar_color(ui.visuals())); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - // Add buttons such as "add new tab" - ui.spacing_mut().item_spacing.x = 0.0; // Tabs have spacing built-in - let scroll_state_id = ui.make_persistent_id(tile_id); let mut scroll_state = ui.ctx().memory_mut(|m| { m.data @@ -197,27 +223,25 @@ impl Tabs { .unwrap_or_default() }); - // Show Right UI Menu (any size allowed) + // Allow user to add buttons such as "add new tab". + // They can als read and modify the scroll state if they want. behavior.top_bar_right_ui(&tree.tiles, ui, tile_id, self, &mut scroll_state.offset); - // We will subtract the button widths from this. let mut scroll_area_width = ui.available_width(); - scroll_state.update(ui.ctx(), &mut scroll_area_width); - - // If consumed > available (by margin), show right scroll icon. - if scroll_state.show_right_arrow && ui.button("⏵").clicked() { - scroll_state.offset_debt += SCROLL_INCREMENT; - } + scroll_state.update(ui, &mut scroll_area_width); - ui.set_clip_rect(ui.available_rect_before_wrap()); // Don't cover the `rtl_ui` buttons. - - let scroll_area_size = vec2(scroll_area_width, ui.available_height()); + // We're in a right-to-left layout, so start with the right scroll-arrow: + scroll_state.right_arrow(ui); ui.allocate_ui_with_layout( - scroll_area_size, + ui.available_size(), egui::Layout::left_to_right(egui::Align::Center), |ui| { + scroll_state.left_arrow(ui); + + // Prepare to show the scroll area with the tabs: + scroll_state.offset = scroll_state .offset .at_most(scroll_state.content_size.x - ui.available_width()); @@ -229,61 +253,61 @@ impl Tabs { .auto_shrink([false; 2]) .horizontal_scroll_offset(scroll_state.offset); - let output = scroll_area.show_viewport(ui, |ui, _| { - ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { - if !tree.is_root(tile_id) { - // Make the background behind the buttons draggable (to drag the parent container tile): - if ui - .interact( - ui.max_rect(), - ui.id().with("background"), - egui::Sense::drag(), - ) - .on_hover_cursor(egui::CursorIcon::Grab) - .drag_started() - { - ui.memory_mut(|mem| mem.set_dragged_id(tile_id.egui_id())); - } + let output = scroll_area.show(ui, |ui| { + if !tree.is_root(tile_id) { + // Make the background behind the buttons draggable (to drag the parent container tile): + if ui + .interact( + ui.max_rect(), + ui.id().with("background"), + egui::Sense::drag(), + ) + .on_hover_cursor(egui::CursorIcon::Grab) + .drag_started() + { + ui.memory_mut(|mem| mem.set_dragged_id(tile_id.egui_id())); } + } - for (i, &child_id) in self.children.iter().enumerate() { - if !tree.is_visible(child_id) { - continue; - } + ui.spacing_mut().item_spacing.x = 0.0; // Tabs have spacing built-in - let is_being_dragged = is_being_dragged(ui.ctx(), child_id); - - let selected = self.is_active(child_id); - let id = child_id.egui_id(); - - let response = behavior.tab_ui( - &tree.tiles, - ui, - id, - child_id, - selected, - is_being_dragged, - ); - let response = response.on_hover_cursor(egui::CursorIcon::Grab); - if response.clicked() { - next_active = Some(child_id); - } + for (i, &child_id) in self.children.iter().enumerate() { + if !tree.is_visible(child_id) { + continue; + } - if let Some(mouse_pos) = drop_context.mouse_pos { - if drop_context.dragged_tile_id.is_some() - && response.rect.contains(mouse_pos) - { - // Expand this tab - maybe the user wants to drop something into it! - next_active = Some(child_id); - } - } + let is_being_dragged = is_being_dragged(ui.ctx(), child_id); + + let selected = self.is_active(child_id); + let id = child_id.egui_id(); + + let response = behavior.tab_ui( + &tree.tiles, + ui, + id, + child_id, + selected, + is_being_dragged, + ); + let response = response.on_hover_cursor(egui::CursorIcon::Grab); + if response.clicked() { + next_active = Some(child_id); + } - button_rects.insert(child_id, response.rect); - if is_being_dragged { - dragged_index = Some(i); + if let Some(mouse_pos) = drop_context.mouse_pos { + if drop_context.dragged_tile_id.is_some() + && response.rect.contains(mouse_pos) + { + // Expand this tab - maybe the user wants to drop something into it! + next_active = Some(child_id); } } - }); + + button_rects.insert(child_id, response.rect); + if is_being_dragged { + dragged_index = Some(i); + } + } }); scroll_state.offset = output.state.offset.x; @@ -292,14 +316,6 @@ impl Tabs { }, ); - if scroll_state.show_left_arrow { - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - if ui.button("⏴").clicked() { - scroll_state.offset_debt -= SCROLL_INCREMENT; - } - }); - } - ui.ctx() .memory_mut(|m| m.data.insert_temp(scroll_state_id, scroll_state)); }); From 5d8255261c4c97c26303a66c29b47f05b174904b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 10 Nov 2023 21:14:38 +0100 Subject: [PATCH 32/33] Fix typo --- src/container/tabs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/container/tabs.rs b/src/container/tabs.rs index 3201957..cd21538 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -225,7 +225,7 @@ impl Tabs { }); // Allow user to add buttons such as "add new tab". - // They can als read and modify the scroll state if they want. + // They can also read and modify the scroll state if they want. behavior.top_bar_right_ui(&tree.tiles, ui, tile_id, self, &mut scroll_state.offset); // We will subtract the button widths from this. let mut scroll_area_width = ui.available_width(); From 60315f26e092e5fc6743360792261afe09cf5858 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 10 Nov 2023 21:18:24 +0100 Subject: [PATCH 33/33] Code cleanup --- src/container/tabs.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/container/tabs.rs b/src/container/tabs.rs index cd21538..8594e1f 100644 --- a/src/container/tabs.rs +++ b/src/container/tabs.rs @@ -52,7 +52,10 @@ struct ScrollState { } impl ScrollState { - pub fn update(&mut self, ui: &egui::Ui, scroll_area_width: &mut f32) { + /// Returns the space left for the tabs after the scroll arrows. + pub fn update(&mut self, ui: &egui::Ui) -> f32 { + let mut scroll_area_width = ui.available_width(); + let button_and_spacing_width = SCROLL_ARROW_SIZE.x + ui.spacing().item_spacing.x; let margin = 0.1; @@ -60,17 +63,17 @@ impl ScrollState { self.show_left_arrow = SCROLL_ARROW_SIZE.x < self.offset; if self.show_left_arrow { - *scroll_area_width -= button_and_spacing_width; + scroll_area_width -= button_and_spacing_width; } - self.show_right_arrow = self.offset + *scroll_area_width + margin < self.content_size.x; + self.show_right_arrow = self.offset + scroll_area_width + margin < self.content_size.x; // Compensate for showing/hiding of arrow: self.offset += button_and_spacing_width * ((self.show_left_arrow as i32 as f32) - (self.showed_left_arrow_prev as i32 as f32)); if self.show_right_arrow { - *scroll_area_width -= button_and_spacing_width; + scroll_area_width -= button_and_spacing_width; } self.showed_left_arrow_prev = self.show_left_arrow; @@ -91,6 +94,8 @@ impl ScrollState { ui.ctx().request_repaint(); } } + + scroll_area_width } fn scroll_increment(&self) -> f32 { @@ -227,10 +232,8 @@ impl Tabs { // Allow user to add buttons such as "add new tab". // They can also read and modify the scroll state if they want. behavior.top_bar_right_ui(&tree.tiles, ui, tile_id, self, &mut scroll_state.offset); - // We will subtract the button widths from this. - let mut scroll_area_width = ui.available_width(); - scroll_state.update(ui, &mut scroll_area_width); + let scroll_area_width = scroll_state.update(ui); // We're in a right-to-left layout, so start with the right scroll-arrow: scroll_state.right_arrow(ui);