From 954376f5252da7101b625c440da4d70a5dff4c9f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 8 Nov 2023 09:14:52 +0100 Subject: [PATCH 01/18] Move scroll bar spacing settings to a `struct ScrollSpacing` --- crates/egui/src/containers/scroll_area.rs | 12 +-- crates/egui/src/style.rs | 104 ++++++++++++++-------- crates/egui_extras/src/table.rs | 6 +- 3 files changed, 78 insertions(+), 44 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 0c2cead7023..072bb078459 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -743,8 +743,8 @@ impl Prepared { } // margin on either side of the scroll bar - let inner_margin = animation_t * ui.spacing().scroll_bar_inner_margin; - let outer_margin = animation_t * ui.spacing().scroll_bar_outer_margin; + let inner_margin = animation_t * ui.spacing().scroll.bar_inner_margin; + let outer_margin = animation_t * ui.spacing().scroll.bar_outer_margin; let mut min_cross = inner_rect.max[1 - d] + inner_margin; // left of vertical scroll (d == 1) let mut max_cross = outer_rect.max[1 - d] - outer_margin; // right of vertical scroll (d == 1) let min_main = inner_rect.min[d]; // top of vertical scroll (d == 1) @@ -855,7 +855,7 @@ impl Prepared { ), ) }; - let min_handle_size = ui.spacing().scroll_handle_min_length; + let min_handle_size = ui.spacing().scroll.handle_min_length; if handle_rect.size()[d] < min_handle_size { handle_rect = Rect::from_center_size( handle_rect.center(), @@ -920,7 +920,7 @@ impl Prepared { /// Width of a vertical scrollbar, or height of a horizontal scroll bar fn max_scroll_bar_width_with_margin(ui: &Ui) -> f32 { - ui.spacing().scroll_bar_inner_margin - + ui.spacing().scroll_bar_width - + ui.spacing().scroll_bar_outer_margin + ui.spacing().scroll.bar_inner_margin + + ui.spacing().scroll.bar_width + + ui.spacing().scroll.bar_outer_margin } diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 3c44b292a6c..652f10bfddf 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -2,11 +2,13 @@ #![allow(clippy::if_same_then_else)] +use std::collections::BTreeMap; + +use epaint::{Rounding, Shadow, Stroke}; + use crate::{ ecolor::*, emath::*, ComboBox, CursorIcon, FontFamily, FontId, Response, RichText, WidgetText, }; -use epaint::{Rounding, Shadow, Stroke}; -use std::collections::BTreeMap; // ---------------------------------------------------------------------------- @@ -303,16 +305,8 @@ pub struct Spacing { /// Height of a combo-box before showing scroll bars. pub combo_height: f32, - pub scroll_bar_width: f32, - - /// Make sure the scroll handle is at least this big - pub scroll_handle_min_length: f32, - - /// Margin between contents and scroll bar. - pub scroll_bar_inner_margin: f32, - - /// Margin between scroll bar and the outer container (e.g. right of a vertical scroll bar). - pub scroll_bar_outer_margin: f32, + /// Controls the spacing of a [`crate::ScrollArea`]. + pub scroll: ScrollSpacing, } impl Spacing { @@ -333,6 +327,64 @@ impl Spacing { // ---------------------------------------------------------------------------- +/// Controls the spacing of a [`crate::ScrollArea`]. +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(default))] +pub struct ScrollSpacing { + pub bar_width: f32, + + /// Make sure the scroll handle is at least this big + pub handle_min_length: f32, + + /// Margin between contents and scroll bar. + pub bar_inner_margin: f32, + + /// Margin between scroll bar and the outer container (e.g. right of a vertical scroll bar). + pub bar_outer_margin: f32, +} + +impl Default for ScrollSpacing { + fn default() -> Self { + Self { + bar_width: 8.0, + handle_min_length: 12.0, + bar_inner_margin: 4.0, + bar_outer_margin: 0.0, + } + } +} + +impl ScrollSpacing { + pub fn ui(&mut self, ui: &mut Ui) { + let Self { + bar_width, + handle_min_length, + bar_inner_margin, + bar_outer_margin, + } = self; + + ui.horizontal(|ui| { + ui.add(DragValue::new(bar_width).clamp_range(0.0..=32.0)); + ui.label("Bar width"); + }); + ui.horizontal(|ui| { + ui.add(DragValue::new(handle_min_length).clamp_range(0.0..=32.0)); + ui.label("Minimum handle length"); + }); + ui.horizontal(|ui| { + ui.add(DragValue::new(bar_inner_margin).clamp_range(0.0..=32.0)); + ui.label("Inner margin"); + }); + ui.horizontal(|ui| { + ui.add(DragValue::new(bar_outer_margin).clamp_range(0.0..=32.0)); + ui.label("Outer margin"); + }); + } +} + +// ---------------------------------------------------------------------------- + #[derive(Clone, Copy, Debug, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Margin { @@ -807,10 +859,7 @@ impl Default for Spacing { icon_spacing: 4.0, tooltip_width: 600.0, combo_height: 200.0, - scroll_bar_width: 8.0, - scroll_handle_min_length: 12.0, - scroll_bar_inner_margin: 4.0, - scroll_bar_outer_margin: 0.0, + scroll: Default::default(), indent_ends_with_horizontal_line: false, } } @@ -1146,10 +1195,7 @@ impl Spacing { tooltip_width, indent_ends_with_horizontal_line, combo_height, - scroll_bar_width, - scroll_handle_min_length, - scroll_bar_inner_margin, - scroll_bar_outer_margin, + scroll, } = self; ui.add(slider_vec2(item_spacing, 0.0..=20.0, "Item spacing")); @@ -1176,21 +1222,9 @@ impl Spacing { ui.add(DragValue::new(text_edit_width).clamp_range(0.0..=1000.0)); ui.label("TextEdit width"); }); - ui.horizontal(|ui| { - ui.add(DragValue::new(scroll_bar_width).clamp_range(0.0..=32.0)); - ui.label("Scroll-bar width"); - }); - ui.horizontal(|ui| { - ui.add(DragValue::new(scroll_handle_min_length).clamp_range(0.0..=32.0)); - ui.label("Scroll-bar handle min length"); - }); - ui.horizontal(|ui| { - ui.add(DragValue::new(scroll_bar_inner_margin).clamp_range(0.0..=32.0)); - ui.label("Scroll-bar inner margin"); - }); - ui.horizontal(|ui| { - ui.add(DragValue::new(scroll_bar_outer_margin).clamp_range(0.0..=32.0)); - ui.label("Scroll-bar outer margin"); + + ui.collapsing("Scroll Area", |ui| { + scroll.ui(ui); }); ui.horizontal(|ui| { diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index c687a7f1c35..ca09b385931 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -366,9 +366,9 @@ impl<'a> TableBuilder<'a> { fn available_width(&self) -> f32 { self.ui.available_rect_before_wrap().width() - if self.scroll_options.vscroll { - self.ui.spacing().scroll_bar_inner_margin - + self.ui.spacing().scroll_bar_width - + self.ui.spacing().scroll_bar_outer_margin + self.ui.spacing().scroll.bar_inner_margin + + self.ui.spacing().scroll.bar_width + + self.ui.spacing().scroll.bar_outer_margin } else { 0.0 } From 7027253071cb90451b887989939cdc7f26a8a3f9 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 8 Nov 2023 09:19:29 +0100 Subject: [PATCH 02/18] Add a demo for changing scroll bar appearance --- crates/egui_demo_lib/src/demo/scrolling.rs | 29 +++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/crates/egui_demo_lib/src/demo/scrolling.rs b/crates/egui_demo_lib/src/demo/scrolling.rs index f5833521b5b..df3960c80c1 100644 --- a/crates/egui_demo_lib/src/demo/scrolling.rs +++ b/crates/egui_demo_lib/src/demo/scrolling.rs @@ -3,6 +3,7 @@ use egui::*; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[derive(Clone, Copy, Debug, PartialEq)] enum ScrollDemo { + ScrollAppearance, ScrollTo, ManyLines, LargeCanvas, @@ -12,7 +13,7 @@ enum ScrollDemo { impl Default for ScrollDemo { fn default() -> Self { - Self::ScrollTo + Self::ScrollAppearance } } @@ -44,6 +45,7 @@ impl super::Demo for Scrolling { impl super::View for Scrolling { fn ui(&mut self, ui: &mut Ui) { ui.horizontal(|ui| { + ui.selectable_value(&mut self.demo, ScrollDemo::ScrollAppearance, "Appearance"); ui.selectable_value(&mut self.demo, ScrollDemo::ScrollTo, "Scroll to"); ui.selectable_value( &mut self.demo, @@ -60,6 +62,9 @@ impl super::View for Scrolling { }); ui.separator(); match self.demo { + ScrollDemo::ScrollAppearance => { + scroll_bar_appearance(ui); + } ScrollDemo::ScrollTo => { self.scroll_to.ui(ui); } @@ -84,6 +89,28 @@ impl super::View for Scrolling { } } +fn scroll_bar_appearance(ui: &mut egui::Ui) { + ui.label("Settings:"); + + let mut style: Style = (*ui.ctx().style()).clone(); + style.spacing.scroll.ui(ui); + if ui.button("Reset").clicked() { + style.spacing.scroll = Default::default(); + } + ui.ctx().set_style(style.clone()); + ui.set_style(style); + + ui.separator(); + + ScrollArea::vertical() + .auto_shrink([false; 2]) + .show(ui, |ui| { + for _ in 0..100 { + ui.label(crate::LOREM_IPSUM_LONG); + } + }); +} + fn huge_content_lines(ui: &mut egui::Ui) { ui.label( "A lot of rows, but only the visible ones are laid out, so performance is still good:", From 80d48f3fd0b973c20db14b54dcfd39adc15ea3e8 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 8 Nov 2023 09:46:07 +0100 Subject: [PATCH 03/18] Add setting for ScrollBarVisibility in demo --- crates/egui/src/containers/scroll_area.rs | 37 ++++++++++-- crates/egui_demo_lib/src/demo/scrolling.rs | 67 ++++++++++++++++------ 2 files changed, 82 insertions(+), 22 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 072bb078459..5ca43ca0746 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -80,11 +80,40 @@ pub struct ScrollAreaOutput { } /// Indicate whether the horizontal and vertical scroll bars must be always visible, hidden or visible when needed. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub enum ScrollBarVisibility { - AlwaysVisible, - VisibleWhenNeeded, + /// Hide scroll bar even if they are needed. + /// + /// You can still scroll, with the scroll-wheel + /// and by dragging the contents, but there is no + /// visual indication of how far you have scrolled. AlwaysHidden, + + /// Show scroll bars only when the content size exceeds the container, + /// i.e. when there is any need to scroll. + /// + /// This is the default. + VisibleWhenNeeded, + + /// Always show the scroll bar, even if the contents fit in the container + /// and there is no need to scroll. + AlwaysVisible, +} + +impl Default for ScrollBarVisibility { + #[inline] + fn default() -> Self { + Self::VisibleWhenNeeded + } +} + +impl ScrollBarVisibility { + pub const ALL: [Self; 3] = [ + Self::AlwaysHidden, + Self::VisibleWhenNeeded, + Self::AlwaysVisible, + ]; } /// Add vertical and/or horizontal scrolling to a contained [`Ui`]. @@ -151,7 +180,7 @@ impl ScrollArea { auto_shrink: [true; 2], max_size: Vec2::INFINITY, min_scrolled_size: Vec2::splat(64.0), - scroll_bar_visibility: ScrollBarVisibility::VisibleWhenNeeded, + scroll_bar_visibility: Default::default(), id_source: None, offset_x: None, offset_y: None, diff --git a/crates/egui_demo_lib/src/demo/scrolling.rs b/crates/egui_demo_lib/src/demo/scrolling.rs index df3960c80c1..049f7ad6e57 100644 --- a/crates/egui_demo_lib/src/demo/scrolling.rs +++ b/crates/egui_demo_lib/src/demo/scrolling.rs @@ -1,4 +1,4 @@ -use egui::*; +use egui::{scroll_area::ScrollBarVisibility, *}; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[derive(Clone, Copy, Debug, PartialEq)] @@ -21,6 +21,7 @@ impl Default for ScrollDemo { #[cfg_attr(feature = "serde", serde(default))] #[derive(Default, PartialEq)] pub struct Scrolling { + appearance: ScrollAppearance, demo: ScrollDemo, scroll_to: ScrollTo, scroll_stick_to: ScrollStickTo, @@ -34,7 +35,7 @@ impl super::Demo for Scrolling { fn show(&mut self, ctx: &egui::Context, open: &mut bool) { egui::Window::new(self.name()) .open(open) - .resizable(false) + .resizable(true) .show(ctx, |ui| { use super::View as _; self.ui(ui); @@ -63,7 +64,7 @@ impl super::View for Scrolling { ui.separator(); match self.demo { ScrollDemo::ScrollAppearance => { - scroll_bar_appearance(ui); + self.appearance.ui(ui); } ScrollDemo::ScrollTo => { self.scroll_to.ui(ui); @@ -89,26 +90,56 @@ impl super::View for Scrolling { } } -fn scroll_bar_appearance(ui: &mut egui::Ui) { - ui.label("Settings:"); +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(default))] +#[derive(Default, PartialEq)] +struct ScrollAppearance { + visibility: ScrollBarVisibility, +} - let mut style: Style = (*ui.ctx().style()).clone(); - style.spacing.scroll.ui(ui); - if ui.button("Reset").clicked() { - style.spacing.scroll = Default::default(); - } - ui.ctx().set_style(style.clone()); - ui.set_style(style); +impl ScrollAppearance { + fn ui(&mut self, ui: &mut egui::Ui) { + let Self { visibility } = self; - ui.separator(); + let mut style: Style = (*ui.ctx().style()).clone(); - ScrollArea::vertical() - .auto_shrink([false; 2]) - .show(ui, |ui| { - for _ in 0..100 { - ui.label(crate::LOREM_IPSUM_LONG); + style.spacing.scroll.ui(ui); + + ui.add_space(8.0); + + ui.horizontal(|ui| { + ui.label("ScrollBarVisibility:"); + for option in ScrollBarVisibility::ALL { + ui.selectable_value(visibility, option, format!("{option:?}")); } }); + + ui.add_space(8.0); + + if ui.button("Reset").clicked() { + style.spacing.scroll = Default::default(); + *visibility = Default::default(); + } + + ui.ctx().set_style(style.clone()); + ui.set_style(style); + + ui.separator(); + + ScrollArea::vertical() + .auto_shrink([false; 2]) + .scroll_bar_visibility(*visibility) + .show(ui, |ui| { + ui.with_layout( + egui::Layout::top_down(egui::Align::LEFT).with_cross_justify(true), + |ui| { + for _ in 0..2 { + ui.label(crate::LOREM_IPSUM_LONG); + } + }, + ); + }); + } } fn huge_content_lines(ui: &mut egui::Ui) { From 73a295adb6aeb414ad1b7984d30028ffb022011d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 8 Nov 2023 09:46:26 +0100 Subject: [PATCH 04/18] Add `#[inline]` to a `ScrollArea` builder methods --- crates/egui/src/containers/scroll_area.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 5ca43ca0746..e2c6c92d950 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -152,22 +152,26 @@ pub struct ScrollArea { impl ScrollArea { /// Create a horizontal scroll area. + #[inline] pub fn horizontal() -> Self { Self::new([true, false]) } /// Create a vertical scroll area. + #[inline] pub fn vertical() -> Self { Self::new([false, true]) } /// Create a bi-directional (horizontal and vertical) scroll area. + #[inline] pub fn both() -> Self { Self::new([true, true]) } /// Create a scroll area where both direction of scrolling is disabled. /// It's unclear why you would want to do this. + #[inline] pub fn neither() -> Self { Self::new([false, false]) } @@ -195,6 +199,7 @@ impl ScrollArea { /// Use `f32::INFINITY` if you want the scroll area to expand to fit the surrounding [`Ui`] (default). /// /// See also [`Self::auto_shrink`]. + #[inline] pub fn max_width(mut self, max_width: f32) -> Self { self.max_size.x = max_width; self @@ -205,6 +210,7 @@ impl ScrollArea { /// Use `f32::INFINITY` if you want the scroll area to expand to fit the surrounding [`Ui`] (default). /// /// See also [`Self::auto_shrink`]. + #[inline] pub fn max_height(mut self, max_height: f32) -> Self { self.max_size.y = max_height; self @@ -216,6 +222,7 @@ impl ScrollArea { /// (and so we don't require scroll bars). /// /// Default: `64.0`. + #[inline] pub fn min_scrolled_width(mut self, min_scrolled_width: f32) -> Self { self.min_scrolled_size.x = min_scrolled_width; self @@ -227,6 +234,7 @@ impl ScrollArea { /// (and so we don't require scroll bars). /// /// Default: `64.0`. + #[inline] pub fn min_scrolled_height(mut self, min_scrolled_height: f32) -> Self { self.min_scrolled_size.y = min_scrolled_height; self @@ -235,12 +243,14 @@ impl ScrollArea { /// Set the visibility of both horizontal and vertical scroll bars. /// /// With `ScrollBarVisibility::VisibleWhenNeeded` (default), the scroll bar will be visible only when needed. + #[inline] pub fn scroll_bar_visibility(mut self, scroll_bar_visibility: ScrollBarVisibility) -> Self { self.scroll_bar_visibility = scroll_bar_visibility; self } /// A source for the unique [`Id`], e.g. `.id_source("second_scroll_area")` or `.id_source(loop_index)`. + #[inline] pub fn id_source(mut self, id_source: impl std::hash::Hash) -> Self { self.id_source = Some(Id::new(id_source)); self @@ -253,6 +263,7 @@ impl ScrollArea { /// See also: [`Self::vertical_scroll_offset`], [`Self::horizontal_scroll_offset`], /// [`Ui::scroll_to_cursor`](crate::ui::Ui::scroll_to_cursor) and /// [`Response::scroll_to_me`](crate::Response::scroll_to_me) + #[inline] pub fn scroll_offset(mut self, offset: Vec2) -> Self { self.offset_x = Some(offset.x); self.offset_y = Some(offset.y); @@ -265,6 +276,7 @@ impl ScrollArea { /// /// See also: [`Self::scroll_offset`], [`Ui::scroll_to_cursor`](crate::ui::Ui::scroll_to_cursor) and /// [`Response::scroll_to_me`](crate::Response::scroll_to_me) + #[inline] pub fn vertical_scroll_offset(mut self, offset: f32) -> Self { self.offset_y = Some(offset); self @@ -276,24 +288,28 @@ impl ScrollArea { /// /// See also: [`Self::scroll_offset`], [`Ui::scroll_to_cursor`](crate::ui::Ui::scroll_to_cursor) and /// [`Response::scroll_to_me`](crate::Response::scroll_to_me) + #[inline] pub fn horizontal_scroll_offset(mut self, offset: f32) -> Self { self.offset_x = Some(offset); self } /// Turn on/off scrolling on the horizontal axis. + #[inline] pub fn hscroll(mut self, hscroll: bool) -> Self { self.has_bar[0] = hscroll; self } /// Turn on/off scrolling on the vertical axis. + #[inline] pub fn vscroll(mut self, vscroll: bool) -> Self { self.has_bar[1] = vscroll; self } /// Turn on/off scrolling on the horizontal/vertical axes. + #[inline] pub fn scroll2(mut self, has_bar: [bool; 2]) -> Self { self.has_bar = has_bar; self @@ -308,6 +324,7 @@ impl ScrollArea { /// is typing text in a [`TextEdit`] widget contained within the scroll area. /// /// This controls both scrolling directions. + #[inline] pub fn enable_scrolling(mut self, enable: bool) -> Self { self.scrolling_enabled = enable; self @@ -320,6 +337,7 @@ impl ScrollArea { /// If `true`, the [`ScrollArea`] will sense drags. /// /// Default: `true`. + #[inline] pub fn drag_to_scroll(mut self, drag_to_scroll: bool) -> Self { self.drag_to_scroll = drag_to_scroll; self @@ -331,6 +349,7 @@ impl ScrollArea { /// * If `false`, egui will add blank space inside the scroll area. /// /// Default: `[true; 2]`. + #[inline] pub fn auto_shrink(mut self, auto_shrink: [bool; 2]) -> Self { self.auto_shrink = auto_shrink; self @@ -346,6 +365,7 @@ impl ScrollArea { /// it will remain focused on whatever content viewport the user left it on. If the scroll /// handle is dragged all the way to the right it will again become stuck and remain there /// until manually pulled from the end position. + #[inline] pub fn stick_to_right(mut self, stick: bool) -> Self { self.stick_to_end[0] = stick; self @@ -357,6 +377,7 @@ impl ScrollArea { /// it will remain focused on whatever content viewport the user left it on. If the scroll /// handle is dragged to the bottom it will again become stuck and remain there until manually /// pulled from the end position. + #[inline] pub fn stick_to_bottom(mut self, stick: bool) -> Self { self.stick_to_end[1] = stick; self From 54968df0dce67b4fe2b719110b0272d643765733 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 8 Nov 2023 11:05:49 +0100 Subject: [PATCH 05/18] Refactor how scroll bar show/hide is computed --- crates/egui/src/containers/scroll_area.rs | 109 ++++++++++++---------- crates/egui/src/containers/window.rs | 2 +- crates/emath/src/vec2.rs | 10 ++ 3 files changed, 71 insertions(+), 50 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index e2c6c92d950..fa62caf225a 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -130,8 +130,9 @@ impl ScrollBarVisibility { #[derive(Clone, Debug)] #[must_use = "You should call .show()"] pub struct ScrollArea { - /// Do we have horizontal/vertical scrolling? - has_bar: [bool; 2], + /// Do we have horizontal/vertical scrolling enabled? + scroll_enabled: [bool; 2], + auto_shrink: [bool; 2], max_size: Vec2, min_scrolled_size: Vec2, @@ -178,9 +179,9 @@ impl ScrollArea { /// Create a scroll area where you decide which axis has scrolling enabled. /// For instance, `ScrollArea::new([true, false])` enables horizontal scrolling. - pub fn new(has_bar: [bool; 2]) -> Self { + pub fn new(scroll_enabled: [bool; 2]) -> Self { Self { - has_bar, + scroll_enabled, auto_shrink: [true; 2], max_size: Vec2::INFINITY, min_scrolled_size: Vec2::splat(64.0), @@ -297,21 +298,21 @@ impl ScrollArea { /// Turn on/off scrolling on the horizontal axis. #[inline] pub fn hscroll(mut self, hscroll: bool) -> Self { - self.has_bar[0] = hscroll; + self.scroll_enabled[0] = hscroll; self } /// Turn on/off scrolling on the vertical axis. #[inline] pub fn vscroll(mut self, vscroll: bool) -> Self { - self.has_bar[1] = vscroll; + self.scroll_enabled[1] = vscroll; self } /// Turn on/off scrolling on the horizontal/vertical axes. #[inline] - pub fn scroll2(mut self, has_bar: [bool; 2]) -> Self { - self.has_bar = has_bar; + pub fn scroll2(mut self, scroll_enabled: [bool; 2]) -> Self { + self.scroll_enabled = scroll_enabled; self } @@ -355,8 +356,9 @@ impl ScrollArea { self } - pub(crate) fn has_any_bar(&self) -> bool { - self.has_bar[0] || self.has_bar[1] + /// Is any scrolling enabled? + pub(crate) fn is_any_scroll_enabled(&self) -> bool { + self.scroll_enabled[0] || self.scroll_enabled[1] } /// The scroll handle will stick to the rightmost position even while the content size @@ -387,11 +389,22 @@ impl ScrollArea { struct Prepared { id: Id, state: State, - has_bar: [bool; 2], + auto_shrink: [bool; 2], + /// Does this `ScrollArea` have horizontal/vertical scrolling enabled? + scroll_enabled: [bool; 2], + + /// Like [`Self::show_bars`], but smoothly interpolated + show_bars_factor: Vec2, + /// How much horizontal and vertical space are used up by the /// width of the vertical bar, and the height of the horizontal bar? + /// + /// Note that this is a `yx` swizzling of [`Self::show_bars_factor`] + /// times the maximum bar with. + /// That's because horizontal scroll uses up vertical space, + /// and vice versa. current_bar_use: Vec2, scroll_bar_visibility: ScrollBarVisibility, @@ -412,7 +425,7 @@ struct Prepared { impl ScrollArea { fn begin(self, ui: &mut Ui) -> Prepared { let Self { - has_bar, + scroll_enabled, auto_shrink, max_size, min_scrolled_size, @@ -441,23 +454,20 @@ impl ScrollArea { let max_scroll_bar_width = max_scroll_bar_width_with_margin(ui); - let current_hscroll_bar_height = if !has_bar[0] { - 0.0 - } else if scroll_bar_visibility == ScrollBarVisibility::AlwaysVisible { - max_scroll_bar_width - } else { - max_scroll_bar_width * ui.ctx().animate_bool(id.with("h"), state.show_scroll[0]) + let show_bars: [bool; 2] = match scroll_bar_visibility { + ScrollBarVisibility::AlwaysHidden => [false; 2], + ScrollBarVisibility::VisibleWhenNeeded => state.show_scroll, + ScrollBarVisibility::AlwaysVisible => scroll_enabled, }; - let current_vscroll_bar_width = if !has_bar[1] { - 0.0 - } else if scroll_bar_visibility == ScrollBarVisibility::AlwaysVisible { - max_scroll_bar_width - } else { - max_scroll_bar_width * ui.ctx().animate_bool(id.with("v"), state.show_scroll[1]) - }; + let show_bars_factor = Vec2::new( + ui.ctx().animate_bool(id.with("h"), show_bars[0]), + ui.ctx().animate_bool(id.with("v"), show_bars[1]), + ); - let current_bar_use = vec2(current_vscroll_bar_width, current_hscroll_bar_height); + // Note the swizzling: hscroll uses up vertical space, + // and vscroll uses up horizontal spae! + let current_bar_use = max_scroll_bar_width * show_bars_factor.yx(); let available_outer = ui.available_rect_before_wrap(); @@ -471,7 +481,7 @@ impl ScrollArea { // one shouldn't collapse into nothingness. // See https://github.com/emilk/egui/issues/1097 for d in 0..2 { - if has_bar[d] { + if scroll_enabled[d] { inner_size[d] = inner_size[d].max(min_scrolled_size[d]); } } @@ -488,7 +498,7 @@ impl ScrollArea { } else { // Tell the inner Ui to use as much space as possible, we can scroll to see it! for d in 0..2 { - if has_bar[d] { + if scroll_enabled[d] { content_max_size[d] = f32::INFINITY; } } @@ -502,7 +512,7 @@ impl ScrollArea { let clip_rect_margin = ui.visuals().clip_rect_margin; let mut content_clip_rect = ui.clip_rect(); for d in 0..2 { - if has_bar[d] { + if scroll_enabled[d] { if state.content_is_too_large[d] { content_clip_rect.min[d] = inner_rect.min[d] - clip_rect_margin; content_clip_rect.max[d] = inner_rect.max[d] + clip_rect_margin; @@ -529,7 +539,7 @@ impl ScrollArea { if content_response.dragged() { for d in 0..2 { - if has_bar[d] { + if scroll_enabled[d] { ui.input(|input| { state.offset[d] -= input.pointer.delta()[d]; state.vel[d] = input.pointer.velocity()[d]; @@ -560,8 +570,9 @@ impl ScrollArea { Prepared { id, state, - has_bar, auto_shrink, + scroll_enabled, + show_bars_factor, current_bar_use, scroll_bar_visibility, inner_rect, @@ -671,9 +682,10 @@ impl Prepared { id, mut state, inner_rect, - has_bar, auto_shrink, - mut current_bar_use, + scroll_enabled, + mut show_bars_factor, + current_bar_use, scroll_bar_visibility, content_ui, viewport: _, @@ -684,7 +696,7 @@ impl Prepared { let content_size = content_ui.min_size(); for d in 0..2 { - if has_bar[d] { + if scroll_enabled[d] { // We take the scroll target so only this ScrollArea will use it: let scroll_target = content_ui .ctx() @@ -730,7 +742,7 @@ impl Prepared { let mut inner_size = inner_rect.size(); for d in 0..2 { - inner_size[d] = match (has_bar[d], auto_shrink[d]) { + inner_size[d] = match (scroll_enabled[d], auto_shrink[d]) { (true, true) => inner_size[d].min(content_size[d]), // shrink scroll area if content is small (true, false) => inner_size[d], // let scroll area be larger than content; fill with blank space (false, true) => content_size[d], // Follow the content (expand/contract to fit it). @@ -744,14 +756,14 @@ impl Prepared { let outer_rect = Rect::from_min_size(inner_rect.min, inner_rect.size() + current_bar_use); let content_is_too_large = [ - content_size.x > inner_rect.width(), - content_size.y > inner_rect.height(), + scroll_enabled[0] && inner_rect.width() < content_size.x, + scroll_enabled[1] && inner_rect.height() < content_size.y, ]; let max_offset = content_size - inner_rect.size(); if scrolling_enabled && ui.rect_contains_pointer(outer_rect) { for d in 0..2 { - if has_bar[d] { + if scroll_enabled[d] { let scroll_delta = ui.ctx().frame_state(|fs| fs.scroll_delta); let scrolling_up = state.offset[d] > 0.0 && scroll_delta[d] > 0.0; @@ -768,25 +780,24 @@ impl Prepared { } let show_scroll_this_frame = match scroll_bar_visibility { - ScrollBarVisibility::AlwaysVisible => [true, true], + ScrollBarVisibility::AlwaysHidden => [false, false], ScrollBarVisibility::VisibleWhenNeeded => { [content_is_too_large[0], content_is_too_large[1]] } - ScrollBarVisibility::AlwaysHidden => [false, false], + ScrollBarVisibility::AlwaysVisible => scroll_enabled, }; - let max_scroll_bar_width = max_scroll_bar_width_with_margin(ui); - // Avoid frame delay; start showing scroll bar right away: - if show_scroll_this_frame[0] && current_bar_use.y <= 0.0 { - current_bar_use.y = max_scroll_bar_width * ui.ctx().animate_bool(id.with("h"), true); + if show_scroll_this_frame[0] && show_bars_factor.x <= 0.0 { + show_bars_factor.x = ui.ctx().animate_bool(id.with("h"), true); } - if show_scroll_this_frame[1] && current_bar_use.x <= 0.0 { - current_bar_use.x = max_scroll_bar_width * ui.ctx().animate_bool(id.with("v"), true); + if show_scroll_this_frame[1] && show_bars_factor.y <= 0.0 { + show_bars_factor.y = ui.ctx().animate_bool(id.with("v"), true); } + // Paint the bars: for d in 0..2 { - let animation_t = current_bar_use[1 - d] / max_scroll_bar_width; + let animation_t = show_bars_factor[d]; if animation_t == 0.0 { continue; @@ -954,9 +965,9 @@ impl Prepared { // has appropriate effect. state.scroll_stuck_to_end = [ (state.offset[0] == available_offset[0]) - || (self.stick_to_end[0] && available_offset[0] < 0.), + || (self.stick_to_end[0] && available_offset[0] < 0.0), (state.offset[1] == available_offset[1]) - || (self.stick_to_end[1] && available_offset[1] < 0.), + || (self.stick_to_end[1] && available_offset[1] < 0.0), ]; state.show_scroll = show_scroll_this_frame; diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index c12199d40ac..6c385bbff39 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -419,7 +419,7 @@ impl<'open> Window<'open> { ui.add_space(title_content_spacing); } - if scroll.has_any_bar() { + if scroll.is_any_scroll_enabled() { scroll.show(ui, add_contents).inner } else { add_contents(ui) diff --git a/crates/emath/src/vec2.rs b/crates/emath/src/vec2.rs index 133c3fd3c20..0e0a9373572 100644 --- a/crates/emath/src/vec2.rs +++ b/crates/emath/src/vec2.rs @@ -274,6 +274,16 @@ impl Vec2 { self.x.max(self.y) } + /// Swizzle the axes. + #[inline] + #[must_use] + pub fn yx(self) -> Vec2 { + Vec2 { + x: self.y, + y: self.x, + } + } + #[must_use] #[inline] pub fn clamp(self, min: Self, max: Self) -> Self { From 180ab6a44d6efb1b6f076df02d73965136bf7d00 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 8 Nov 2023 12:52:21 +0100 Subject: [PATCH 06/18] Add support for floating scroll bars --- crates/egui/src/containers/scroll_area.rs | 199 +++++++++++++++------ crates/egui/src/style.rs | 112 +++++++++++- crates/egui_demo_lib/src/demo/scrolling.rs | 25 ++- crates/emath/src/rect.rs | 28 +++ 4 files changed, 299 insertions(+), 65 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index fa62caf225a..4555943aa70 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -1,8 +1,3 @@ -//! Coordinate system names: -//! * content: size of contents (generally large; that's why we want scroll bars) -//! * outer: size of scroll area including scroll bar(s) -//! * inner: excluding scroll bar(s). The area we clip the contents to. - #![allow(clippy::needless_range_loop)] use crate::*; @@ -20,6 +15,9 @@ pub struct State { /// The content were to large to fit large frame. content_is_too_large: [bool; 2], + /// Did the user interact (hover or drag) the scroll bars last frame? + scroll_bar_interaction: [bool; 2], + /// Momentum, used for kinetic scrolling #[cfg_attr(feature = "serde", serde(skip))] vel: Vec2, @@ -39,6 +37,7 @@ impl Default for State { offset: Vec2::ZERO, show_scroll: [false; 2], content_is_too_large: [false; 2], + scroll_bar_interaction: [false; 2], vel: Vec2::ZERO, scroll_start_offset_from_top_left: [None; 2], scroll_stuck_to_end: [true; 2], @@ -118,6 +117,35 @@ impl ScrollBarVisibility { /// Add vertical and/or horizontal scrolling to a contained [`Ui`]. /// +/// By default, scroll bars only show up when needed, i.e. when the contents +/// is larger than the container. +/// This is controlled by [`Self::scroll_bar_visibility`]. +/// +/// There are two flavors of scroll areas: solid and floating. +/// Solid scroll bars use up space, reducing the amount of space available +/// to the contents. Floating scroll bars float on top of the contents, covering it. +/// You can control with with [`ScrollSpacing::floating`]. +/// +/// ### Floating scroll baers +/// Floating scroll bars are _dormant_ by default, meaning they are +/// tranlucent and thin. +/// Exactly how tranlucent is controlled by [`ScrollSpacing::floating_dormant_opacity`]. +/// +/// When the user hovers the scroll _area_, the scroll bars become opaque, but stay thin. +/// Their thickness is controlled by [`ScrollSpacing::floating_thickness`]. +/// +/// When the user hovers the scroll _bars_, they become wider so +/// the user can more easily grab onto them. +/// How wide is controlled by [`ScrollSpacing::bar_width`]. +/// +/// ### Coordinate system +/// * content: size of contents (generally large; that's why we want scroll bars) +/// * outer: size of scroll area including scroll bar(s) +/// * inner: excluding scroll bar(s). The area we clip the contents to. +/// +/// If the floating scroll bars settings is turned on then `inner == outer`. +/// +/// ## Example /// ``` /// # egui::__run_test_ui(|ui| { /// egui::ScrollArea::vertical().show(ui, |ui| { @@ -401,6 +429,8 @@ struct Prepared { /// How much horizontal and vertical space are used up by the /// width of the vertical bar, and the height of the horizontal bar? /// + /// This is always zero for floating scroll bars. + /// /// Note that this is a `yx` swizzling of [`Self::show_bars_factor`] /// times the maximum bar with. /// That's because horizontal scroll uses up vertical space, @@ -442,7 +472,7 @@ impl ScrollArea { let id_source = id_source.unwrap_or_else(|| Id::new("scroll_area")); let id = ui.make_persistent_id(id_source); - ui.ctx().check_for_id_clash( + ctx.check_for_id_clash( id, Rect::from_min_size(ui.available_rect_before_wrap().min, Vec2::ZERO), "ScrollArea", @@ -452,8 +482,6 @@ impl ScrollArea { state.offset.x = offset_x.unwrap_or(state.offset.x); state.offset.y = offset_y.unwrap_or(state.offset.y); - let max_scroll_bar_width = max_scroll_bar_width_with_margin(ui); - let show_bars: [bool; 2] = match scroll_bar_visibility { ScrollBarVisibility::AlwaysHidden => [false; 2], ScrollBarVisibility::VisibleWhenNeeded => state.show_scroll, @@ -461,13 +489,18 @@ impl ScrollArea { }; let show_bars_factor = Vec2::new( - ui.ctx().animate_bool(id.with("h"), show_bars[0]), - ui.ctx().animate_bool(id.with("v"), show_bars[1]), + ctx.animate_bool(id.with("h"), show_bars[0]), + ctx.animate_bool(id.with("v"), show_bars[1]), ); - // Note the swizzling: hscroll uses up vertical space, - // and vscroll uses up horizontal spae! - let current_bar_use = max_scroll_bar_width * show_bars_factor.yx(); + let current_bar_use = if ui.spacing().scroll.floating { + // Floating scroll bars don't take up space - they float! + Vec2::ZERO + } else { + // Note the swizzling: hscroll uses up vertical space, + // and vscroll uses up horizontal space! + show_bars_factor.yx() * ui.spacing().scroll.max_width_with_margin() + }; let available_outer = ui.available_rect_before_wrap(); @@ -562,7 +595,7 @@ impl ScrollArea { // Offset has an inverted coordinate system compared to // the velocity, so we subtract it instead of adding it state.offset -= state.vel * dt; - ui.ctx().request_repaint(); + ctx.request_repaint(); } } } @@ -761,7 +794,8 @@ impl Prepared { ]; let max_offset = content_size - inner_rect.size(); - if scrolling_enabled && ui.rect_contains_pointer(outer_rect) { + let is_hovering_outer_rect = ui.rect_contains_pointer(outer_rect); + if scrolling_enabled && is_hovering_outer_rect { for d in 0..2 { if scroll_enabled[d] { let scroll_delta = ui.ctx().frame_state(|fs| fs.scroll_delta); @@ -795,23 +829,54 @@ impl Prepared { show_bars_factor.y = ui.ctx().animate_bool(id.with("v"), true); } + let scroll_style = ui.spacing().scroll; + // Paint the bars: for d in 0..2 { - let animation_t = show_bars_factor[d]; - - if animation_t == 0.0 { + let show_factor = show_bars_factor[d]; + if show_factor == 0.0 { + state.scroll_bar_interaction[d] = false; continue; } - // margin on either side of the scroll bar - let inner_margin = animation_t * ui.spacing().scroll.bar_inner_margin; - let outer_margin = animation_t * ui.spacing().scroll.bar_outer_margin; - let mut min_cross = inner_rect.max[1 - d] + inner_margin; // left of vertical scroll (d == 1) - let mut max_cross = outer_rect.max[1 - d] - outer_margin; // right of vertical scroll (d == 1) - let min_main = inner_rect.min[d]; // top of vertical scroll (d == 1) - let max_main = inner_rect.max[d]; // bottom of vertical scroll (d == 1) + // left/right of a horizontal scroll (d==1) + // top/bottom of vertical scroll (d == 1) + let main_range = Rangef::new(inner_rect.min[d], inner_rect.max[d]); - if ui.clip_rect().max[1 - d] < max_cross + outer_margin { + // Margin on either side of the scroll bar: + let inner_margin = show_factor * scroll_style.bar_inner_margin; + let outer_margin = show_factor * scroll_style.bar_outer_margin; + + // top/bottom of a horizontal scroll (d==0). + // left/rigth of a vertical scroll (d==1). + let mut cross = if scroll_style.floating { + let max_bar_rect = if d == 0 { + outer_rect.with_min_y(outer_rect.max.y - scroll_style.max_width_with_margin()) + } else { + outer_rect.with_min_x(outer_rect.max.x - scroll_style.max_width_with_margin()) + }; + let is_hovering_bar_area = is_hovering_outer_rect + && ui.rect_contains_pointer(max_bar_rect) + || state.scroll_bar_interaction[d]; + let is_hovering_bar_area_t = ui + .ctx() + .animate_bool(id.with("bar_hover"), is_hovering_bar_area); + let width = show_factor + * lerp( + scroll_style.floating_width..=scroll_style.bar_width, + is_hovering_bar_area_t, + ); + + let max_cross = outer_rect.max[1 - d] - outer_margin; + let min_cross = max_cross - width; + Rangef::new(min_cross, max_cross) + } else { + let min_cross = inner_rect.max[1 - d] + inner_margin; + let max_cross = outer_rect.max[1 - d] - outer_margin; + Rangef::new(min_cross, max_cross) + }; + + if ui.clip_rect().max[1 - d] < cross.max + outer_margin { // Move the scrollbar so it is visible. This is needed in some cases. // For instance: // * When we have a vertical-only scroll area in a top level panel, @@ -821,20 +886,20 @@ impl Prepared { // is outside the clip rectangle. // Really this should use the tighter clip_rect that ignores clip_rect_margin, but we don't store that. // clip_rect_margin is quite a hack. It would be nice to get rid of it. - let width = max_cross - min_cross; - max_cross = ui.clip_rect().max[1 - d] - outer_margin; - min_cross = max_cross - width; + let width = cross.max - cross.min; + cross.max = ui.clip_rect().max[1 - d] - outer_margin; + cross.min = cross.max - width; } let outer_scroll_rect = if d == 0 { Rect::from_min_max( - pos2(inner_rect.left(), min_cross), - pos2(inner_rect.right(), max_cross), + pos2(inner_rect.left(), cross.min), + pos2(inner_rect.right(), cross.max), ) } else { Rect::from_min_max( - pos2(min_cross, inner_rect.top()), - pos2(max_cross, inner_rect.bottom()), + pos2(cross.min, inner_rect.top()), + pos2(cross.max, inner_rect.bottom()), ) }; @@ -843,19 +908,18 @@ impl Prepared { state.offset[d] = content_size[d] - inner_rect.size()[d]; } - let from_content = - |content| remap_clamp(content, 0.0..=content_size[d], min_main..=max_main); + let from_content = |content| remap_clamp(content, 0.0..=content_size[d], main_range); let handle_rect = if d == 0 { Rect::from_min_max( - pos2(from_content(state.offset.x), min_cross), - pos2(from_content(state.offset.x + inner_rect.width()), max_cross), + pos2(from_content(state.offset.x), cross.min), + pos2(from_content(state.offset.x + inner_rect.width()), cross.max), ) } else { Rect::from_min_max( - pos2(min_cross, from_content(state.offset.y)), + pos2(cross.min, from_content(state.offset.y)), pos2( - max_cross, + cross.max, from_content(state.offset.y + inner_rect.height()), ), ) @@ -869,22 +933,24 @@ impl Prepared { }; let response = ui.interact(outer_scroll_rect, interact_id, sense); + state.scroll_bar_interaction[d] = response.hovered() || response.dragged(); + if let Some(pointer_pos) = response.interact_pointer_pos() { let scroll_start_offset_from_top_left = state.scroll_start_offset_from_top_left[d] .get_or_insert_with(|| { if handle_rect.contains(pointer_pos) { pointer_pos[d] - handle_rect.min[d] } else { - let handle_top_pos_at_bottom = max_main - handle_rect.size()[d]; + let handle_top_pos_at_bottom = main_range.max - handle_rect.size()[d]; // Calculate the new handle top position, centering the handle on the mouse. let new_handle_top_pos = (pointer_pos[d] - handle_rect.size()[d] / 2.0) - .clamp(min_main, handle_top_pos_at_bottom); + .clamp(main_range.min, handle_top_pos_at_bottom); pointer_pos[d] - new_handle_top_pos } }); let new_handle_top = pointer_pos[d] - *scroll_start_offset_from_top_left; - state.offset[d] = remap(new_handle_top, min_main..=max_main, 0.0..=content_size[d]); + state.offset[d] = remap(new_handle_top, main_range, 0.0..=content_size[d]); // some manual action taken, scroll not stuck state.scroll_stuck_to_end[d] = false; @@ -904,19 +970,19 @@ impl Prepared { // Avoid frame-delay by calculating a new handle rect: let mut handle_rect = if d == 0 { Rect::from_min_max( - pos2(from_content(state.offset.x), min_cross), - pos2(from_content(state.offset.x + inner_rect.width()), max_cross), + pos2(from_content(state.offset.x), cross.min), + pos2(from_content(state.offset.x + inner_rect.width()), cross.max), ) } else { Rect::from_min_max( - pos2(min_cross, from_content(state.offset.y)), + pos2(cross.min, from_content(state.offset.y)), pos2( - max_cross, + cross.max, from_content(state.offset.y + inner_rect.height()), ), ) }; - let min_handle_size = ui.spacing().scroll.handle_min_length; + let min_handle_size = scroll_style.handle_min_length; if handle_rect.size()[d] < min_handle_size { handle_rect = Rect::from_center_size( handle_rect.center(), @@ -934,16 +1000,44 @@ impl Prepared { &ui.style().visuals.widgets.inactive }; + let handle_opacity = if scroll_style.floating { + if response.hovered() || response.dragged() { + scroll_style.interact_handle_opacity + } else if is_hovering_outer_rect { + scroll_style.active_handle_opacity + } else { + scroll_style.dormant_handle_opacity + } + } else { + 1.0 + }; + + let background_opacity = if scroll_style.floating { + if response.hovered() || response.dragged() { + scroll_style.interact_background_opacity + } else if is_hovering_outer_rect { + scroll_style.active_background_opacity + } else { + scroll_style.dormant_background_opacity + } + } else { + 1.0 + }; + + // Background: ui.painter().add(epaint::Shape::rect_filled( outer_scroll_rect, visuals.rounding, - ui.visuals().extreme_bg_color, + ui.visuals() + .extreme_bg_color + .gamma_multiply(background_opacity), )); + // Handle: ui.painter().add(epaint::Shape::rect_filled( handle_rect, visuals.rounding, - visuals.bg_fill, + visuals.bg_fill.gamma_multiply(handle_opacity), )); } } @@ -978,10 +1072,3 @@ impl Prepared { (content_size, state) } } - -/// Width of a vertical scrollbar, or height of a horizontal scroll bar -fn max_scroll_bar_width_with_margin(ui: &Ui) -> f32 { - ui.spacing().scroll.bar_inner_margin - + ui.spacing().scroll.bar_width - + ui.spacing().scroll.bar_outer_margin -} diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 652f10bfddf..7ff21e85ca5 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -328,10 +328,17 @@ impl Spacing { // ---------------------------------------------------------------------------- /// Controls the spacing of a [`crate::ScrollArea`]. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] pub struct ScrollSpacing { + /// If `true`, scroll bars float above the content, partially covering it. + /// + /// If `false`, the scroll bars allocate space, shrinking the area + /// available to the contents. + pub floating: bool, + + /// The width of the scroll bars at it largest. pub bar_width: f32, /// Make sure the scroll handle is at least this big @@ -341,45 +348,138 @@ pub struct ScrollSpacing { pub bar_inner_margin: f32, /// Margin between scroll bar and the outer container (e.g. right of a vertical scroll bar). + /// Only makes sense for non-floating scroll bars. pub bar_outer_margin: f32, + + /// The thin width of floating scroll bars that the user is NOT hovering. + /// + /// When the user hovers the scroll bars they expand to [`Self::bar_width`]. + pub floating_width: f32, + + /// The opaqueness of the handle when the user is neither scrolling + /// nor hovering the scroll area. + pub dormant_handle_opacity: f32, + + /// The opaqueness of the handle when the user is hovering + /// the scroll area, but not the scroll bar. + pub active_handle_opacity: f32, + + /// The opaqueness of the handle when the user is hovering + /// over the scroll bars. + pub interact_handle_opacity: f32, + + /// The opaqueness of the background when the user is neither scrolling + /// nor hovering the scroll area. + pub dormant_background_opacity: f32, + + /// The opaqueness of the background when the user is hovering + /// the scroll area, but not the scroll bar. + pub active_background_opacity: f32, + + /// The opaqueness of the background when the user is hovering + /// over the scroll bars. + pub interact_background_opacity: f32, } impl Default for ScrollSpacing { fn default() -> Self { Self { + floating: false, bar_width: 8.0, handle_min_length: 12.0, bar_inner_margin: 4.0, bar_outer_margin: 0.0, + floating_width: 3.0, + + dormant_handle_opacity: 0.0, + active_handle_opacity: 0.9, + interact_handle_opacity: 1.0, + + dormant_background_opacity: 0.0, + active_background_opacity: 0.4, + interact_background_opacity: 0.7, } } } impl ScrollSpacing { + /// Width of a solid vertical scrollbar, or height of a horizontal scroll bar, when it is at its widest. + pub fn max_width_with_margin(&self) -> f32 { + self.bar_inner_margin + self.bar_width + self.bar_outer_margin + } + pub fn ui(&mut self, ui: &mut Ui) { let Self { + floating, bar_width, handle_min_length, bar_inner_margin, bar_outer_margin, + floating_width, + + dormant_handle_opacity, + active_handle_opacity, + interact_handle_opacity, + dormant_background_opacity, + active_background_opacity, + interact_background_opacity, } = self; + ui.horizontal(|ui| { + ui.label("Type:"); + ui.selectable_value(floating, false, "Solid"); + ui.selectable_value(floating, true, "Floating"); + }); + ui.horizontal(|ui| { ui.add(DragValue::new(bar_width).clamp_range(0.0..=32.0)); - ui.label("Bar width"); + ui.label("Full bar width"); }); + if *floating { + ui.horizontal(|ui| { + ui.add(DragValue::new(floating_width).clamp_range(0.0..=32.0)); + ui.label("Thin bar width"); + }); + } + ui.horizontal(|ui| { ui.add(DragValue::new(handle_min_length).clamp_range(0.0..=32.0)); ui.label("Minimum handle length"); }); - ui.horizontal(|ui| { - ui.add(DragValue::new(bar_inner_margin).clamp_range(0.0..=32.0)); - ui.label("Inner margin"); - }); ui.horizontal(|ui| { ui.add(DragValue::new(bar_outer_margin).clamp_range(0.0..=32.0)); ui.label("Outer margin"); }); + if *floating { + crate::Grid::new("opacity").show(ui, |ui| { + fn opacity_ui(ui: &mut Ui, opacity: &mut f32) { + ui.add(DragValue::new(opacity).speed(0.01).clamp_range(0.0..=1.0)); + } + + ui.label("Opacity"); + ui.label("Dormant"); + ui.label("Active"); + ui.label("Interacting"); + ui.end_row(); + + ui.label("Background:"); + opacity_ui(ui, dormant_background_opacity); + opacity_ui(ui, active_background_opacity); + opacity_ui(ui, interact_background_opacity); + ui.end_row(); + + ui.label("Handle:"); + opacity_ui(ui, dormant_handle_opacity); + opacity_ui(ui, active_handle_opacity); + opacity_ui(ui, interact_handle_opacity); + ui.end_row(); + }); + } else { + ui.horizontal(|ui| { + ui.add(DragValue::new(bar_inner_margin).clamp_range(0.0..=32.0)); + ui.label("Inner margin"); + }); + } } } diff --git a/crates/egui_demo_lib/src/demo/scrolling.rs b/crates/egui_demo_lib/src/demo/scrolling.rs index 049f7ad6e57..e9c582ab0ed 100644 --- a/crates/egui_demo_lib/src/demo/scrolling.rs +++ b/crates/egui_demo_lib/src/demo/scrolling.rs @@ -36,6 +36,8 @@ impl super::Demo for Scrolling { egui::Window::new(self.name()) .open(open) .resizable(true) + .hscroll(false) + .vscroll(false) .show(ctx, |ui| { use super::View as _; self.ui(ui); @@ -92,14 +94,27 @@ impl super::View for Scrolling { #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] -#[derive(Default, PartialEq)] +#[derive(PartialEq)] struct ScrollAppearance { + num_lorem_ipsums: usize, visibility: ScrollBarVisibility, } +impl Default for ScrollAppearance { + fn default() -> Self { + Self { + num_lorem_ipsums: 10, + visibility: ScrollBarVisibility::default(), + } + } +} + impl ScrollAppearance { fn ui(&mut self, ui: &mut egui::Ui) { - let Self { visibility } = self; + let Self { + num_lorem_ipsums, + visibility, + } = self; let mut style: Style = (*ui.ctx().style()).clone(); @@ -126,6 +141,10 @@ impl ScrollAppearance { ui.separator(); + ui.add(egui::Slider::new(num_lorem_ipsums, 1..=100).text("Content length")); + + ui.separator(); + ScrollArea::vertical() .auto_shrink([false; 2]) .scroll_bar_visibility(*visibility) @@ -133,7 +152,7 @@ impl ScrollAppearance { ui.with_layout( egui::Layout::top_down(egui::Align::LEFT).with_cross_justify(true), |ui| { - for _ in 0..2 { + for _ in 0..*num_lorem_ipsums { ui.label(crate::LOREM_IPSUM_LONG); } }, diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index f2d037cae72..8a5670fdf02 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -150,6 +150,34 @@ impl Rect { rect } + #[inline] + #[must_use] + pub fn with_min_x(mut self, min_x: f32) -> Self { + self.min.x = min_x; + self + } + + #[inline] + #[must_use] + pub fn with_min_y(mut self, min_y: f32) -> Self { + self.min.y = min_y; + self + } + + #[inline] + #[must_use] + pub fn with_max_x(mut self, max_x: f32) -> Self { + self.max.x = max_x; + self + } + + #[inline] + #[must_use] + pub fn with_max_y(mut self, max_y: f32) -> Self { + self.max.y = max_y; + self + } + /// Expand by this much in each direction, keeping the center #[must_use] pub fn expand(self, amnt: f32) -> Self { From 2236aefc02826c8397720902e0da548abd19563a Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 8 Nov 2023 14:03:31 +0100 Subject: [PATCH 07/18] Tweak color and opacity of the scroll handle --- crates/egui/src/containers/scroll_area.rs | 27 +++++++++++-- crates/egui/src/style.rs | 49 ++++++++++++----------- 2 files changed, 50 insertions(+), 26 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 4555943aa70..6e1fd005445 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -995,9 +995,24 @@ impl Prepared { } let visuals = if scrolling_enabled { - ui.style().interact(&response) + // Pick visuals based on interaction with the handle. + // Remember that the response is for the whole scroll bar! + let is_hovering_handle = response.hovered() + && ui.input(|i| { + i.pointer + .latest_pos() + .map_or(false, |p| handle_rect.contains(p)) + }); + let visuals = ui.visuals(); + if response.is_pointer_button_down_on() { + &visuals.widgets.active + } else if is_hovering_handle { + &visuals.widgets.hovered + } else { + &visuals.widgets.inactive + } } else { - &ui.style().visuals.widgets.inactive + &ui.visuals().widgets.inactive }; let handle_opacity = if scroll_style.floating { @@ -1024,6 +1039,12 @@ impl Prepared { 1.0 }; + let handle_color = if scroll_style.floating { + visuals.fg_stroke.color + } else { + visuals.bg_fill + }; + // Background: ui.painter().add(epaint::Shape::rect_filled( outer_scroll_rect, @@ -1037,7 +1058,7 @@ impl Prepared { ui.painter().add(epaint::Shape::rect_filled( handle_rect, visuals.rounding, - visuals.bg_fill.gamma_multiply(handle_opacity), + handle_color.gamma_multiply(handle_opacity), )); } } diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 7ff21e85ca5..278efccbc25 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -306,7 +306,7 @@ pub struct Spacing { pub combo_height: f32, /// Controls the spacing of a [`crate::ScrollArea`]. - pub scroll: ScrollSpacing, + pub scroll: ScrollStyle, } impl Spacing { @@ -331,11 +331,14 @@ impl Spacing { #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] -pub struct ScrollSpacing { +pub struct ScrollStyle { /// If `true`, scroll bars float above the content, partially covering it. /// /// If `false`, the scroll bars allocate space, shrinking the area /// available to the contents. + /// + /// This also changes the colors of the scroll-handle to make + /// it more promiment. pub floating: bool, /// The width of the scroll bars at it largest. @@ -356,18 +359,6 @@ pub struct ScrollSpacing { /// When the user hovers the scroll bars they expand to [`Self::bar_width`]. pub floating_width: f32, - /// The opaqueness of the handle when the user is neither scrolling - /// nor hovering the scroll area. - pub dormant_handle_opacity: f32, - - /// The opaqueness of the handle when the user is hovering - /// the scroll area, but not the scroll bar. - pub active_handle_opacity: f32, - - /// The opaqueness of the handle when the user is hovering - /// over the scroll bars. - pub interact_handle_opacity: f32, - /// The opaqueness of the background when the user is neither scrolling /// nor hovering the scroll area. pub dormant_background_opacity: f32, @@ -379,9 +370,21 @@ pub struct ScrollSpacing { /// The opaqueness of the background when the user is hovering /// over the scroll bars. pub interact_background_opacity: f32, + + /// The opaqueness of the handle when the user is neither scrolling + /// nor hovering the scroll area. + pub dormant_handle_opacity: f32, + + /// The opaqueness of the handle when the user is hovering + /// the scroll area, but not the scroll bar. + pub active_handle_opacity: f32, + + /// The opaqueness of the handle when the user is hovering + /// over the scroll bars. + pub interact_handle_opacity: f32, } -impl Default for ScrollSpacing { +impl Default for ScrollStyle { fn default() -> Self { Self { floating: false, @@ -391,18 +394,18 @@ impl Default for ScrollSpacing { bar_outer_margin: 0.0, floating_width: 3.0, - dormant_handle_opacity: 0.0, - active_handle_opacity: 0.9, - interact_handle_opacity: 1.0, - dormant_background_opacity: 0.0, active_background_opacity: 0.4, interact_background_opacity: 0.7, + + dormant_handle_opacity: 0.0, + active_handle_opacity: 0.6, + interact_handle_opacity: 1.0, } } } -impl ScrollSpacing { +impl ScrollStyle { /// Width of a solid vertical scrollbar, or height of a horizontal scroll bar, when it is at its widest. pub fn max_width_with_margin(&self) -> f32 { self.bar_inner_margin + self.bar_width + self.bar_outer_margin @@ -417,12 +420,12 @@ impl ScrollSpacing { bar_outer_margin, floating_width, - dormant_handle_opacity, - active_handle_opacity, - interact_handle_opacity, dormant_background_opacity, active_background_opacity, interact_background_opacity, + dormant_handle_opacity, + active_handle_opacity, + interact_handle_opacity, } = self; ui.horizontal(|ui| { From 4a9f30acfae890f262d4e5337b3cdf36b884be8a Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 8 Nov 2023 14:26:40 +0100 Subject: [PATCH 08/18] Allow allocating a fixed size even for floating scroll bars --- crates/egui/src/containers/scroll_area.rs | 25 +++++++------- crates/egui/src/style.rs | 42 +++++++++++++++++++++-- 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 6e1fd005445..d0c29fc6d30 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -493,14 +493,7 @@ impl ScrollArea { ctx.animate_bool(id.with("v"), show_bars[1]), ); - let current_bar_use = if ui.spacing().scroll.floating { - // Floating scroll bars don't take up space - they float! - Vec2::ZERO - } else { - // Note the swizzling: hscroll uses up vertical space, - // and vscroll uses up horizontal space! - show_bars_factor.yx() * ui.spacing().scroll.max_width_with_margin() - }; + let current_bar_use = show_bars_factor.yx() * ui.spacing().scroll.allocated_width(); let available_outer = ui.available_rect_before_wrap(); @@ -851,9 +844,9 @@ impl Prepared { // left/rigth of a vertical scroll (d==1). let mut cross = if scroll_style.floating { let max_bar_rect = if d == 0 { - outer_rect.with_min_y(outer_rect.max.y - scroll_style.max_width_with_margin()) + outer_rect.with_min_y(outer_rect.max.y - scroll_style.allocated_width()) } else { - outer_rect.with_min_x(outer_rect.max.x - scroll_style.max_width_with_margin()) + outer_rect.with_min_x(outer_rect.max.x - scroll_style.allocated_width()) }; let is_hovering_bar_area = is_hovering_outer_rect && ui.rect_contains_pointer(max_bar_rect) @@ -1018,10 +1011,16 @@ impl Prepared { let handle_opacity = if scroll_style.floating { if response.hovered() || response.dragged() { scroll_style.interact_handle_opacity - } else if is_hovering_outer_rect { - scroll_style.active_handle_opacity } else { - scroll_style.dormant_handle_opacity + let is_hovering_outer_rect_t = ui.ctx().animate_bool( + id.with("is_hovering_outer_rect"), + is_hovering_outer_rect, + ); + lerp( + scroll_style.dormant_handle_opacity + ..=scroll_style.active_handle_opacity, + is_hovering_outer_rect_t, + ) } } else { 1.0 diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 278efccbc25..0ab5c6e16a8 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -359,28 +359,54 @@ pub struct ScrollStyle { /// When the user hovers the scroll bars they expand to [`Self::bar_width`]. pub floating_width: f32, + /// How much space i allocated for a floating scroll bar? + /// + /// Normally this is zero, but you could set this to something small + /// like 4.0 and set [`Self::dormant_handle_opacity`] and + /// [`Self::dormant_background_opacity`] to e.g. 0.5 + /// so as to always show a thin scroll bar. + pub floating_allocated_width: f32, + /// The opaqueness of the background when the user is neither scrolling /// nor hovering the scroll area. + /// + /// This is only for floating scroll bars. + /// Solid scroll bars are always opaque. pub dormant_background_opacity: f32, /// The opaqueness of the background when the user is hovering /// the scroll area, but not the scroll bar. + /// + /// This is only for floating scroll bars. + /// Solid scroll bars are always opaque. pub active_background_opacity: f32, /// The opaqueness of the background when the user is hovering /// over the scroll bars. + /// + /// This is only for floating scroll bars. + /// Solid scroll bars are always opaque. pub interact_background_opacity: f32, /// The opaqueness of the handle when the user is neither scrolling /// nor hovering the scroll area. + /// + /// This is only for floating scroll bars. + /// Solid scroll bars are always opaque. pub dormant_handle_opacity: f32, /// The opaqueness of the handle when the user is hovering /// the scroll area, but not the scroll bar. + /// + /// This is only for floating scroll bars. + /// Solid scroll bars are always opaque. pub active_handle_opacity: f32, /// The opaqueness of the handle when the user is hovering /// over the scroll bars. + /// + /// This is only for floating scroll bars. + /// Solid scroll bars are always opaque. pub interact_handle_opacity: f32, } @@ -392,7 +418,8 @@ impl Default for ScrollStyle { handle_min_length: 12.0, bar_inner_margin: 4.0, bar_outer_margin: 0.0, - floating_width: 3.0, + floating_width: 2.0, + floating_allocated_width: 0.0, dormant_background_opacity: 0.0, active_background_opacity: 0.4, @@ -407,8 +434,12 @@ impl Default for ScrollStyle { impl ScrollStyle { /// Width of a solid vertical scrollbar, or height of a horizontal scroll bar, when it is at its widest. - pub fn max_width_with_margin(&self) -> f32 { - self.bar_inner_margin + self.bar_width + self.bar_outer_margin + pub fn allocated_width(&self) -> f32 { + if self.floating { + self.floating_allocated_width + } else { + self.bar_inner_margin + self.bar_width + self.bar_outer_margin + } } pub fn ui(&mut self, ui: &mut Ui) { @@ -419,6 +450,7 @@ impl ScrollStyle { bar_inner_margin, bar_outer_margin, floating_width, + floating_allocated_width, dormant_background_opacity, active_background_opacity, @@ -443,6 +475,10 @@ impl ScrollStyle { ui.add(DragValue::new(floating_width).clamp_range(0.0..=32.0)); ui.label("Thin bar width"); }); + ui.horizontal(|ui| { + ui.add(DragValue::new(floating_allocated_width).clamp_range(0.0..=32.0)); + ui.label("Allocated width"); + }); } ui.horizontal(|ui| { From 49c805efe581216b0fab89877f453bd342ebbdac Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 9 Nov 2023 09:41:03 +0100 Subject: [PATCH 09/18] Add three pre-sets of scroll bars: solid, thin, floating --- crates/egui/src/containers/scroll_area.rs | 2 +- crates/egui/src/style.rs | 69 ++++++++++++++++++++++- 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index d0c29fc6d30..f3aefeb3de7 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -1038,7 +1038,7 @@ impl Prepared { 1.0 }; - let handle_color = if scroll_style.floating { + let handle_color = if scroll_style.foreground_color { visuals.fg_stroke.color } else { visuals.bg_fill diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 0ab5c6e16a8..774dbe49922 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -367,6 +367,9 @@ pub struct ScrollStyle { /// so as to always show a thin scroll bar. pub floating_allocated_width: f32, + /// If true, use colors with more contrast. Good for floating scroll bars. + pub foreground_color: bool, + /// The opaqueness of the background when the user is neither scrolling /// nor hovering the scroll area. /// @@ -412,15 +415,24 @@ pub struct ScrollStyle { impl Default for ScrollStyle { fn default() -> Self { + Self::solid() + } +} + +impl ScrollStyle { + /// Solid scroll bars that always use up space + pub fn solid() -> Self { Self { floating: false, - bar_width: 8.0, + bar_width: 6.0, handle_min_length: 12.0, bar_inner_margin: 4.0, bar_outer_margin: 0.0, floating_width: 2.0, floating_allocated_width: 0.0, + foreground_color: false, + dormant_background_opacity: 0.0, active_background_opacity: 0.4, interact_background_opacity: 0.7, @@ -430,9 +442,44 @@ impl Default for ScrollStyle { interact_handle_opacity: 1.0, } } -} -impl ScrollStyle { + /// Thin scroll bars that expand on hover + pub fn thin() -> Self { + Self { + floating: true, + bar_width: 12.0, + floating_allocated_width: 6.0, + foreground_color: false, + + dormant_background_opacity: 1.0, + dormant_handle_opacity: 1.0, + + active_background_opacity: 1.0, + active_handle_opacity: 1.0, + + // Be tranlucent when expanded so we can see the content + interact_background_opacity: 0.6, + interact_handle_opacity: 0.6, + + ..Self::solid() + } + } + + /// No scroll bars until you hover the scroll area, + /// at which time they appear faintly, and then expand + /// when you hover the scroll bars. + pub fn floating() -> Self { + Self { + floating: true, + bar_width: 12.0, + foreground_color: true, + floating_allocated_width: 0.0, + dormant_background_opacity: 0.0, + dormant_handle_opacity: 0.0, + ..Self::solid() + } + } + /// Width of a solid vertical scrollbar, or height of a horizontal scroll bar, when it is at its widest. pub fn allocated_width(&self) -> f32 { if self.floating { @@ -443,6 +490,13 @@ impl ScrollStyle { } pub fn ui(&mut self, ui: &mut Ui) { + ui.horizontal(|ui| { + ui.label("Presets:"); + ui.selectable_value(self, Self::solid(), "Solid"); + ui.selectable_value(self, Self::thin(), "Thin"); + ui.selectable_value(self, Self::floating(), "Floating"); + }); + let Self { floating, bar_width, @@ -452,6 +506,8 @@ impl ScrollStyle { floating_width, floating_allocated_width, + foreground_color, + dormant_background_opacity, active_background_opacity, interact_background_opacity, @@ -489,6 +545,13 @@ impl ScrollStyle { ui.add(DragValue::new(bar_outer_margin).clamp_range(0.0..=32.0)); ui.label("Outer margin"); }); + + ui.horizontal(|ui| { + ui.label("Color:"); + ui.selectable_value(foreground_color, false, "Background"); + ui.selectable_value(foreground_color, true, "Foreground"); + }); + if *floating { crate::Grid::new("opacity").show(ui, |ui| { fn opacity_ui(ui: &mut Ui, opacity: &mut f32) { From 42aa76bcf21a301dc616fe47563bdc68eca66689 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 9 Nov 2023 10:37:57 +0100 Subject: [PATCH 10/18] Use floating scroll bars as the default --- crates/egui/src/style.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 774dbe49922..b166e277397 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -415,7 +415,7 @@ pub struct ScrollStyle { impl Default for ScrollStyle { fn default() -> Self { - Self::solid() + Self::floating() } } From a89883cfa9bf59ab72cce05ee581904710df7700 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 9 Nov 2023 10:48:58 +0100 Subject: [PATCH 11/18] Fix id-clash with bidir scroll areas --- crates/egui/src/containers/scroll_area.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index f3aefeb3de7..f2a2569bbb0 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -853,7 +853,7 @@ impl Prepared { || state.scroll_bar_interaction[d]; let is_hovering_bar_area_t = ui .ctx() - .animate_bool(id.with("bar_hover"), is_hovering_bar_area); + .animate_bool(id.with((d, "bar_hover")), is_hovering_bar_area); let width = show_factor * lerp( scroll_style.floating_width..=scroll_style.bar_width, @@ -1013,7 +1013,7 @@ impl Prepared { scroll_style.interact_handle_opacity } else { let is_hovering_outer_rect_t = ui.ctx().animate_bool( - id.with("is_hovering_outer_rect"), + id.with((d, "is_hovering_outer_rect")), is_hovering_outer_rect, ); lerp( From 20f6625acf4eb6e039faa1a8a08da1132edca0b5 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 9 Nov 2023 10:51:35 +0100 Subject: [PATCH 12/18] Improve demo --- crates/egui/src/style.rs | 6 ++++++ crates/egui_demo_lib/src/demo/scrolling.rs | 9 +++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index b166e277397..4b2cc707946 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -497,6 +497,12 @@ impl ScrollStyle { ui.selectable_value(self, Self::floating(), "Floating"); }); + ui.collapsing("Details", |ui| { + self.details_ui(ui); + }); + } + + pub fn details_ui(&mut self, ui: &mut Ui) { let Self { floating, bar_width, diff --git a/crates/egui_demo_lib/src/demo/scrolling.rs b/crates/egui_demo_lib/src/demo/scrolling.rs index e9c582ab0ed..f4452592510 100644 --- a/crates/egui_demo_lib/src/demo/scrolling.rs +++ b/crates/egui_demo_lib/src/demo/scrolling.rs @@ -103,7 +103,7 @@ struct ScrollAppearance { impl Default for ScrollAppearance { fn default() -> Self { Self { - num_lorem_ipsums: 10, + num_lorem_ipsums: 2, visibility: ScrollBarVisibility::default(), } } @@ -128,6 +128,7 @@ impl ScrollAppearance { ui.selectable_value(visibility, option, format!("{option:?}")); } }); + ui.weak("When to show scroll bars; resize the window to see the effect."); ui.add_space(8.0); @@ -141,7 +142,11 @@ impl ScrollAppearance { ui.separator(); - ui.add(egui::Slider::new(num_lorem_ipsums, 1..=100).text("Content length")); + ui.add( + egui::Slider::new(num_lorem_ipsums, 1..=100) + .text("Content length") + .logarithmic(true), + ); ui.separator(); From f33a9a87ccac4a5e861e9d7ae7154d2695b0c35b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 9 Nov 2023 10:51:45 +0100 Subject: [PATCH 13/18] Fix doclink --- crates/egui/src/animation_manager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/animation_manager.rs b/crates/egui/src/animation_manager.rs index be181507962..b7b7d18beff 100644 --- a/crates/egui/src/animation_manager.rs +++ b/crates/egui/src/animation_manager.rs @@ -25,7 +25,7 @@ struct ValueAnim { } impl AnimationManager { - /// See `Context::animate_bool` for documentation + /// See [`crate::Context::animate_bool`] for documentation pub fn animate_bool( &mut self, input: &InputState, From 90619f26aa0087b96e384f55bb2fd46a1d3442fb Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 9 Nov 2023 11:01:15 +0100 Subject: [PATCH 14/18] Remove reset button from demo --- crates/egui_demo_lib/src/demo/scrolling.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/crates/egui_demo_lib/src/demo/scrolling.rs b/crates/egui_demo_lib/src/demo/scrolling.rs index f4452592510..ddcf95550a0 100644 --- a/crates/egui_demo_lib/src/demo/scrolling.rs +++ b/crates/egui_demo_lib/src/demo/scrolling.rs @@ -132,11 +132,6 @@ impl ScrollAppearance { ui.add_space(8.0); - if ui.button("Reset").clicked() { - style.spacing.scroll = Default::default(); - *visibility = Default::default(); - } - ui.ctx().set_style(style.clone()); ui.set_style(style); From 3b339e728585322d8a7d46ef578471f1123ddbbf Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 9 Nov 2023 11:04:21 +0100 Subject: [PATCH 15/18] Fix doclinks --- crates/egui/src/containers/scroll_area.rs | 14 +------------- crates/egui/src/style.rs | 7 ++++++- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index f2a2569bbb0..a1d23053426 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -124,19 +124,7 @@ impl ScrollBarVisibility { /// There are two flavors of scroll areas: solid and floating. /// Solid scroll bars use up space, reducing the amount of space available /// to the contents. Floating scroll bars float on top of the contents, covering it. -/// You can control with with [`ScrollSpacing::floating`]. -/// -/// ### Floating scroll baers -/// Floating scroll bars are _dormant_ by default, meaning they are -/// tranlucent and thin. -/// Exactly how tranlucent is controlled by [`ScrollSpacing::floating_dormant_opacity`]. -/// -/// When the user hovers the scroll _area_, the scroll bars become opaque, but stay thin. -/// Their thickness is controlled by [`ScrollSpacing::floating_thickness`]. -/// -/// When the user hovers the scroll _bars_, they become wider so -/// the user can more easily grab onto them. -/// How wide is controlled by [`ScrollSpacing::bar_width`]. +/// You can change the scroll style by changing the [`crate::style::Spacing::scroll`]. /// /// ### Coordinate system /// * content: size of contents (generally large; that's why we want scroll bars) diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 4b2cc707946..ba0cc1b9e83 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -327,7 +327,12 @@ impl Spacing { // ---------------------------------------------------------------------------- -/// Controls the spacing of a [`crate::ScrollArea`]. +/// Controls the spacing and visuals of a [`crate::ScrollArea`]. +/// +/// There are three presets to chose from: +/// * [`Self::solid`] +/// * [`Self::thin`] +/// * [`Self::floating`] #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] From b79503d43bf4033411d956f9ab531157e05c7f48 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 9 Nov 2023 11:17:20 +0100 Subject: [PATCH 16/18] Fix visual artifact with thin rounded rectangles --- crates/egui_demo_lib/src/demo/misc_demo_window.rs | 2 +- crates/epaint/src/tessellator.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/egui_demo_lib/src/demo/misc_demo_window.rs b/crates/egui_demo_lib/src/demo/misc_demo_window.rs index 195b79e223a..a90465cc19d 100644 --- a/crates/egui_demo_lib/src/demo/misc_demo_window.rs +++ b/crates/egui_demo_lib/src/demo/misc_demo_window.rs @@ -371,7 +371,7 @@ impl BoxPainting { ui.painter().rect( rect, self.rounding, - Color32::from_gray(64), + ui.visuals().text_color().gamma_multiply(0.5), Stroke::new(self.stroke_width, Color32::WHITE), ); } diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index 49cbd31198f..9771362ba37 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -535,6 +535,7 @@ pub mod path { add_circle_quadrant(path, pos2(min.x + r.sw, max.y - r.sw), r.sw, 1.0); add_circle_quadrant(path, pos2(min.x + r.nw, min.y + r.nw), r.nw, 2.0); add_circle_quadrant(path, pos2(max.x - r.ne, min.y + r.ne), r.ne, 3.0); + path.dedup(); // We get duplicates for thin rectangles, producing visual artifats } } From 8de37bafb9d1fb96659518942991c4bddab4737d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 9 Nov 2023 11:37:38 +0100 Subject: [PATCH 17/18] Fix doclink --- crates/egui/src/containers/scroll_area.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index a1d23053426..50036770f0b 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -411,7 +411,7 @@ struct Prepared { /// Does this `ScrollArea` have horizontal/vertical scrolling enabled? scroll_enabled: [bool; 2], - /// Like [`Self::show_bars`], but smoothly interpolated + /// Smoothly interpolated boolean of wether or not to show the scroll bars. show_bars_factor: Vec2, /// How much horizontal and vertical space are used up by the From 4eb72f3472001dca5aa02aabb8ac30c85f889403 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 9 Nov 2023 18:07:02 +0100 Subject: [PATCH 18/18] typos --- crates/egui/src/containers/scroll_area.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 50036770f0b..1cacef47808 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -411,7 +411,7 @@ struct Prepared { /// Does this `ScrollArea` have horizontal/vertical scrolling enabled? scroll_enabled: [bool; 2], - /// Smoothly interpolated boolean of wether or not to show the scroll bars. + /// Smoothly interpolated boolean of whether or not to show the scroll bars. show_bars_factor: Vec2, /// How much horizontal and vertical space are used up by the