From 41f9df5cb3323eb33d58739eecf3247d6b826657 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 9 Nov 2023 18:41:58 +0100 Subject: [PATCH 01/19] Floating scroll bars (#3539) * Move scroll bar spacing settings to a `struct ScrollSpacing` * Add a demo for changing scroll bar appearance * Add setting for ScrollBarVisibility in demo * Add `#[inline]` to a `ScrollArea` builder methods * Refactor how scroll bar show/hide is computed * Add support for floating scroll bars * Tweak color and opacity of the scroll handle * Allow allocating a fixed size even for floating scroll bars * Add three pre-sets of scroll bars: solid, thin, floating * Use floating scroll bars as the default * Fix id-clash with bidir scroll areas * Improve demo * Fix doclink * Remove reset button from demo * Fix doclinks * Fix visual artifact with thin rounded rectangles * Fix doclink * typos --- crates/egui/src/animation_manager.rs | 2 +- crates/egui/src/containers/scroll_area.rs | 366 +++++++++++++----- crates/egui/src/containers/window.rs | 2 +- crates/egui/src/style.rs | 317 +++++++++++++-- .../src/demo/misc_demo_window.rs | 2 +- crates/egui_demo_lib/src/demo/scrolling.rs | 83 +++- crates/egui_extras/src/table.rs | 6 +- crates/emath/src/rect.rs | 28 ++ crates/emath/src/vec2.rs | 10 + crates/epaint/src/tessellator.rs | 1 + 10 files changed, 668 insertions(+), 149 deletions(-) diff --git a/crates/egui/src/animation_manager.rs b/crates/egui/src/animation_manager.rs index be1815079..b7b7d18be 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, diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 0c2cead70..1cacef478 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], @@ -80,15 +79,61 @@ 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`]. /// +/// 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 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) +/// * 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| { @@ -101,8 +146,9 @@ pub enum 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, @@ -123,35 +169,39 @@ 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]) } /// 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), - scroll_bar_visibility: ScrollBarVisibility::VisibleWhenNeeded, + scroll_bar_visibility: Default::default(), id_source: None, offset_x: None, offset_y: None, @@ -166,6 +216,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 @@ -176,6 +227,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 @@ -187,6 +239,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 @@ -198,6 +251,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 @@ -206,12 +260,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 @@ -224,6 +280,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); @@ -236,6 +293,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 @@ -247,26 +305,30 @@ 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.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. - pub fn scroll2(mut self, has_bar: [bool; 2]) -> Self { - self.has_bar = has_bar; + #[inline] + pub fn scroll2(mut self, scroll_enabled: [bool; 2]) -> Self { + self.scroll_enabled = scroll_enabled; self } @@ -279,6 +341,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 @@ -291,6 +354,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 @@ -302,13 +366,15 @@ 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 } - 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 @@ -317,6 +383,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 @@ -328,6 +395,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 @@ -337,11 +405,24 @@ 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], + + /// 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 /// 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, + /// and vice versa. current_bar_use: Vec2, scroll_bar_visibility: ScrollBarVisibility, @@ -362,7 +443,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, @@ -379,7 +460,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", @@ -389,25 +470,18 @@ 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 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( + ctx.animate_bool(id.with("h"), show_bars[0]), + ctx.animate_bool(id.with("v"), show_bars[1]), + ); - let current_bar_use = vec2(current_vscroll_bar_width, current_hscroll_bar_height); + let current_bar_use = show_bars_factor.yx() * ui.spacing().scroll.allocated_width(); let available_outer = ui.available_rect_before_wrap(); @@ -421,7 +495,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]); } } @@ -438,7 +512,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; } } @@ -452,7 +526,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; @@ -479,7 +553,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]; @@ -502,7 +576,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(); } } } @@ -510,8 +584,9 @@ impl ScrollArea { Prepared { id, state, - has_bar, auto_shrink, + scroll_enabled, + show_bars_factor, current_bar_use, scroll_bar_visibility, inner_rect, @@ -621,9 +696,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: _, @@ -634,7 +710,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() @@ -680,7 +756,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). @@ -694,14 +770,15 @@ 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) { + let is_hovering_outer_rect = ui.rect_contains_pointer(outer_rect); + if scrolling_enabled && is_hovering_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; @@ -718,39 +795,69 @@ 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); } + let scroll_style = ui.spacing().scroll; + + // Paint the bars: for d in 0..2 { - let animation_t = current_bar_use[1 - d] / max_scroll_bar_width; - - 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.allocated_width()) + } else { + 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) + || state.scroll_bar_interaction[d]; + let is_hovering_bar_area_t = ui + .ctx() + .animate_bool(id.with((d, "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, @@ -760,20 +867,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()), ) }; @@ -782,19 +889,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()), ), ) @@ -808,22 +914,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; @@ -843,19 +951,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(), @@ -868,21 +976,76 @@ 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 { + if response.hovered() || response.dragged() { + scroll_style.interact_handle_opacity + } else { + let is_hovering_outer_rect_t = ui.ctx().animate_bool( + id.with((d, "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 + }; + + 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 + }; + + let handle_color = if scroll_style.foreground_color { + visuals.fg_stroke.color + } else { + visuals.bg_fill + }; + + // 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, + handle_color.gamma_multiply(handle_opacity), )); } } @@ -904,9 +1067,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; @@ -917,10 +1080,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/containers/window.rs b/crates/egui/src/containers/window.rs index c12199d40..6c385bbff 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/egui/src/style.rs b/crates/egui/src/style.rs index 3c44b292a..ba0cc1b9e 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: ScrollStyle, } impl Spacing { @@ -333,6 +327,277 @@ impl Spacing { // ---------------------------------------------------------------------------- +/// 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))] +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. + 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). + /// 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, + + /// 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, + + /// 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. + /// + /// 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, +} + +impl Default for ScrollStyle { + fn default() -> Self { + Self::floating() + } +} + +impl ScrollStyle { + /// Solid scroll bars that always use up space + pub fn solid() -> Self { + Self { + floating: false, + 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, + + dormant_handle_opacity: 0.0, + active_handle_opacity: 0.6, + interact_handle_opacity: 1.0, + } + } + + /// 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 { + self.floating_allocated_width + } else { + self.bar_inner_margin + self.bar_width + self.bar_outer_margin + } + } + + 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"); + }); + + ui.collapsing("Details", |ui| { + self.details_ui(ui); + }); + } + + pub fn details_ui(&mut self, ui: &mut Ui) { + let Self { + floating, + bar_width, + handle_min_length, + bar_inner_margin, + bar_outer_margin, + floating_width, + floating_allocated_width, + + foreground_color, + + dormant_background_opacity, + active_background_opacity, + interact_background_opacity, + dormant_handle_opacity, + active_handle_opacity, + interact_handle_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("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(floating_allocated_width).clamp_range(0.0..=32.0)); + ui.label("Allocated 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_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) { + 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"); + }); + } + } +} + +// ---------------------------------------------------------------------------- + #[derive(Clone, Copy, Debug, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Margin { @@ -807,10 +1072,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 +1408,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 +1435,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_demo_lib/src/demo/misc_demo_window.rs b/crates/egui_demo_lib/src/demo/misc_demo_window.rs index 195b79e22..a90465cc1 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/egui_demo_lib/src/demo/scrolling.rs b/crates/egui_demo_lib/src/demo/scrolling.rs index f5833521b..ddcf95550 100644 --- a/crates/egui_demo_lib/src/demo/scrolling.rs +++ b/crates/egui_demo_lib/src/demo/scrolling.rs @@ -1,8 +1,9 @@ -use egui::*; +use egui::{scroll_area::ScrollBarVisibility, *}; #[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 } } @@ -20,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, @@ -33,7 +35,9 @@ 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) + .hscroll(false) + .vscroll(false) .show(ctx, |ui| { use super::View as _; self.ui(ui); @@ -44,6 +48,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 +65,9 @@ impl super::View for Scrolling { }); ui.separator(); match self.demo { + ScrollDemo::ScrollAppearance => { + self.appearance.ui(ui); + } ScrollDemo::ScrollTo => { self.scroll_to.ui(ui); } @@ -84,6 +92,75 @@ impl super::View for Scrolling { } } +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(default))] +#[derive(PartialEq)] +struct ScrollAppearance { + num_lorem_ipsums: usize, + visibility: ScrollBarVisibility, +} + +impl Default for ScrollAppearance { + fn default() -> Self { + Self { + num_lorem_ipsums: 2, + visibility: ScrollBarVisibility::default(), + } + } +} + +impl ScrollAppearance { + fn ui(&mut self, ui: &mut egui::Ui) { + let Self { + num_lorem_ipsums, + visibility, + } = self; + + let mut style: Style = (*ui.ctx().style()).clone(); + + 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.weak("When to show scroll bars; resize the window to see the effect."); + + ui.add_space(8.0); + + ui.ctx().set_style(style.clone()); + ui.set_style(style); + + ui.separator(); + + ui.add( + egui::Slider::new(num_lorem_ipsums, 1..=100) + .text("Content length") + .logarithmic(true), + ); + + 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..*num_lorem_ipsums { + 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:", diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index c687a7f1c..ca09b3859 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 } diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index f2d037cae..8a5670fdf 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 { diff --git a/crates/emath/src/vec2.rs b/crates/emath/src/vec2.rs index 133c3fd3c..0e0a93735 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 { diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index 49cbd3119..9771362ba 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 e9f92fee4c288b8146c901cd72d299151d935ac5 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Fri, 10 Nov 2023 05:12:52 -0500 Subject: [PATCH 02/19] Fix some typos (#3459) * Fix typo * Change from what to was It doesn't say WHAT changed only that there WAS a change --- crates/egui/src/response.rs | 4 ++-- crates/egui_plot/src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index e95d07c20..e0c473c19 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -78,7 +78,7 @@ pub struct Response { #[doc(hidden)] pub interact_pointer_pos: Option, - /// What the underlying data changed? + /// Was the underlying data changed? /// /// e.g. the slider was dragged, text was entered in a [`TextEdit`](crate::TextEdit) etc. /// Always `false` for something like a [`Button`](crate::Button). @@ -339,7 +339,7 @@ impl Response { self.is_pointer_button_down_on } - /// What the underlying data changed? + /// Was the underlying data changed? /// /// e.g. the slider was dragged, text was entered in a [`TextEdit`](crate::TextEdit) etc. /// Always `false` for something like a [`Button`](crate::Button). diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index 111f5f65c..7e8507a62 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -1405,7 +1405,7 @@ impl PlotUi { Vec2::new(delta.x / dp_dv[0] as f32, delta.y / dp_dv[1] as f32) } - /// Read the transform netween plot coordinates and screen coordinates. + /// Read the transform between plot coordinates and screen coordinates. pub fn transform(&self) -> &PlotTransform { &self.last_plot_transform } From 7e2c65a82ae561ca48615de75d86a3f1d3c6a5c4 Mon Sep 17 00:00:00 2001 From: YgorSouza <43298013+YgorSouza@users.noreply.github.com> Date: Fri, 10 Nov 2023 11:16:38 +0100 Subject: [PATCH 03/19] Fix upside down slider in the vertical orientation (#3424) --- crates/egui/src/widgets/slider.rs | 4 +++- crates/emath/src/range.rs | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index 72bceb327..182019b95 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -703,7 +703,9 @@ impl<'a> Slider<'a> { let handle_radius = self.handle_radius(rect); match self.orientation { SliderOrientation::Horizontal => rect.x_range().shrink(handle_radius), - SliderOrientation::Vertical => rect.y_range().shrink(handle_radius), + // The vertical case has to be flipped because the largest slider value maps to the + // lowest y value (which is at the top) + SliderOrientation::Vertical => rect.y_range().shrink(handle_radius).flip(), } } diff --git a/crates/emath/src/range.rs b/crates/emath/src/range.rs index b7b975c45..6de7b2a09 100644 --- a/crates/emath/src/range.rs +++ b/crates/emath/src/range.rs @@ -97,6 +97,16 @@ impl Rangef { } } + /// Flip the min and the max + #[inline] + #[must_use] + pub fn flip(self) -> Self { + Self { + min: self.max, + max: self.min, + } + } + /// The overlap of two ranges, i.e. the range that is contained by both. /// /// If the ranges do not overlap, returns a range with `span() < 0.0`. From 6326ef18d70caf6652fe664504c97daea8b91bd0 Mon Sep 17 00:00:00 2001 From: YgorSouza <43298013+YgorSouza@users.noreply.github.com> Date: Fri, 10 Nov 2023 11:17:16 +0100 Subject: [PATCH 04/19] Make slider step account for range start (#3488) Closes #3483 --- crates/egui/src/widgets/slider.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index 182019b95..07c4f410a 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -508,7 +508,8 @@ impl<'a> Slider<'a> { value = emath::round_to_decimals(value, max_decimals); } if let Some(step) = self.step { - value = (value / step).round() * step; + let start = *self.range.start(); + value = start + ((value - start) / step).round() * step; } set(&mut self.get_set_value, value); } From 7169f28ddf58f171dabcbe76c869c2166d1c7bd2 Mon Sep 17 00:00:00 2001 From: Ryan Hileman Date: Fri, 10 Nov 2023 02:18:16 -0800 Subject: [PATCH 05/19] grammar fix in pr template (#3514) --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c00eac197..5578a9fa2 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -9,7 +9,7 @@ Please read the "Making a PR" section of [`CONTRIBUTING.md`](https://github.com/ * Open the PR as a draft until you have self-reviewed it and run `./scripts/check.sh`. * When you have addressed a PR comment, mark it as resolved. -Please be patient! I will review you PR, but my time is limited! +Please be patient! I will review your PR, but my time is limited! --> Closes . From d0ff09ac20c69bf97af10d18e955fcce1754d4cd Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Fri, 10 Nov 2023 04:32:30 -0600 Subject: [PATCH 06/19] Update accesskit and accesskit_winit. (#3475) * Update accesskit and accesskit_winit. * Remove duplicated `libgtk-3-dev` --------- Co-authored-by: Emil Ernerfeldt --- .github/workflows/rust.yml | 2 +- Cargo.lock | 87 +++++++++++++------- crates/egui-winit/Cargo.toml | 2 +- crates/egui/Cargo.toml | 2 +- crates/egui/src/context.rs | 11 ++- crates/egui/src/id.rs | 2 +- crates/egui/src/response.rs | 12 +-- crates/egui/src/widgets/text_edit/builder.rs | 6 +- 8 files changed, 77 insertions(+), 47 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 2b952ef6b..167d18dbb 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -96,7 +96,7 @@ jobs: toolchain: 1.70.0 targets: wasm32-unknown-unknown - - run: sudo apt-get update && sudo apt-get install libgtk-3-dev + - run: sudo apt-get update && sudo apt-get install libgtk-3-dev libatk1.0-dev - name: Set up cargo cache uses: Swatinem/rust-cache@v2 diff --git a/Cargo.lock b/Cargo.lock index 986f156f8..1500d9e29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,9 +20,9 @@ checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" [[package]] name = "accesskit" -version = "0.11.2" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76eb1adf08c5bcaa8490b9851fd53cca27fa9880076f178ea9d29f05196728a8" +checksum = "b0cc53b7e5d8f45ebe687178cf91af0f45fdba6e78fedf94f0269c5be5b9f296" dependencies = [ "enumn", "serde", @@ -30,18 +30,18 @@ dependencies = [ [[package]] name = "accesskit_consumer" -version = "0.15.2" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04bb4d9e4772fe0d47df57d0d5dbe5d85dd05e2f37ae1ddb6b105e76be58fb00" +checksum = "39dfcfd32eb0c1b525daaf4b02adcd2fa529c22cd713491e15bf002a01a714f5" dependencies = [ "accesskit", ] [[package]] name = "accesskit_macos" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134d0acf6acb667c89d3332999b1a5df4edbc8d6113910f392ebb73f2b03bb56" +checksum = "89c7e8406319ac3149d7b59983637984f0864bbf738319b1c443976268b6426c" dependencies = [ "accesskit", "accesskit_consumer", @@ -51,38 +51,40 @@ dependencies = [ [[package]] name = "accesskit_unix" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e084cb5168790c0c112626175412dc5ad127083441a8248ae49ddf6725519e83" +checksum = "0b0c84552a7995c981d5f22e2d4b24ba9a55718bb12fba883506d6d7344acaf1" dependencies = [ "accesskit", "accesskit_consumer", "async-channel", + "async-once-cell", "atspi", "futures-lite", + "once_cell", "serde", "zbus", ] [[package]] name = "accesskit_windows" -version = "0.14.3" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eac0a7f2d7cd7a93b938af401d3d8e8b7094217989a7c25c55a953023436e31" +checksum = "314d4a797fc82d182b04f4f0665a368924fb556ad9557fccd2d39d38dc8c1c1b" dependencies = [ "accesskit", "accesskit_consumer", - "arrayvec", "once_cell", "paste", + "static_assertions", "windows 0.48.0", ] [[package]] name = "accesskit_winit" -version = "0.14.4" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "825d23acee1bd6d25cbaa3ca6ed6e73faf24122a774ec33d52c5c86c6ab423c0" +checksum = "88e39fcec2e10971e188730b7a76bab60647dacc973d4591855ebebcadfaa738" dependencies = [ "accesskit", "accesskit_macos", @@ -299,6 +301,12 @@ dependencies = [ "event-listener 2.5.3", ] +[[package]] +name = "async-once-cell" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9338790e78aa95a416786ec8389546c4b6a1dfc3dc36071ed9518a9413a542eb" + [[package]] name = "async-process" version = "1.8.0" @@ -383,29 +391,50 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "atspi" -version = "0.10.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "674e7a3376837b2e7d12d34d58ac47073c491dc3bf6f71a7adaf687d4d817faa" +checksum = "6059f350ab6f593ea00727b334265c4dfc7fd442ee32d264794bd9bdc68e87ca" dependencies = [ - "async-recursion", - "async-trait", - "atspi-macros", - "enumflags2", - "futures-lite", - "serde", - "tracing", - "zbus", - "zbus_names", + "atspi-common", + "atspi-connection", + "atspi-proxies", ] [[package]] -name = "atspi-macros" -version = "0.2.0" +name = "atspi-common" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb4870a32c0eaa17e35bca0e6b16020635157121fb7d45593d242c295bc768" +checksum = "92af95f966d2431f962bc632c2e68eda7777330158bf640c4af4249349b2cdf5" dependencies = [ - "quote", - "syn 1.0.109", + "enumflags2", + "serde", + "static_assertions", + "zbus", + "zbus_names", + "zvariant", +] + +[[package]] +name = "atspi-connection" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c65e7d70f86d4c0e3b2d585d9bf3f979f0b19d635a336725a88d279f76b939" +dependencies = [ + "atspi-common", + "atspi-proxies", + "futures-lite", + "zbus", +] + +[[package]] +name = "atspi-proxies" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6495661273703e7a229356dcbe8c8f38223d697aacfaf0e13590a9ac9977bb52" +dependencies = [ + "atspi-common", + "serde", + "zbus", ] [[package]] diff --git a/crates/egui-winit/Cargo.toml b/crates/egui-winit/Cargo.toml index e48c2b074..0c8434850 100644 --- a/crates/egui-winit/Cargo.toml +++ b/crates/egui-winit/Cargo.toml @@ -66,7 +66,7 @@ winit = { version = "0.28", default-features = false } #! ### Optional dependencies # feature accesskit -accesskit_winit = { version = "0.14.0", optional = true } +accesskit_winit = { version = "0.15.0", optional = true } ## Enable this when generating docs. document-features = { version = "0.2", optional = true } diff --git a/crates/egui/Cargo.toml b/crates/egui/Cargo.toml index 349f0849a..145f1d63c 100644 --- a/crates/egui/Cargo.toml +++ b/crates/egui/Cargo.toml @@ -85,7 +85,7 @@ ahash = { version = "0.8.1", default-features = false, features = [ nohash-hasher = "0.2" #! ### Optional dependencies -accesskit = { version = "0.11", optional = true } +accesskit = { version = "0.12", optional = true } backtrace = { version = "0.3", optional = true } diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 27060021b..168489752 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1291,7 +1291,6 @@ impl Context { crate::profile_scope!("accesskit"); let state = self.frame_state_mut(|fs| fs.accesskit_state.take()); if let Some(state) = state { - let has_focus = self.input(|i| i.raw.focused); let root_id = crate::accesskit_root_id().accesskit_id(); let nodes = self.write(|ctx| { state @@ -1305,13 +1304,13 @@ impl Context { }) .collect() }); + let focus_id = self + .memory(|mem| mem.focus()) + .map_or(root_id, |id| id.accesskit_id()); platform_output.accesskit_update = Some(accesskit::TreeUpdate { nodes, tree: Some(accesskit::Tree::new(root_id)), - focus: has_focus.then(|| { - let focus_id = self.memory(|mem| mem.focus()); - focus_id.map_or(root_id, |id| id.accesskit_id()) - }), + focus: focus_id, }); } } @@ -1941,7 +1940,7 @@ impl Context { NodeBuilder::new(Role::Window).build(&mut ctx.accesskit_node_classes), )], tree: Some(Tree::new(root_id)), - focus: None, + focus: root_id, }) } } diff --git a/crates/egui/src/id.rs b/crates/egui/src/id.rs index 612314312..75cc9f856 100644 --- a/crates/egui/src/id.rs +++ b/crates/egui/src/id.rs @@ -72,7 +72,7 @@ impl Id { #[cfg(feature = "accesskit")] pub(crate) fn accesskit_id(&self) -> accesskit::NodeId { - std::num::NonZeroU64::new(self.0).unwrap().into() + self.0.into() } } diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index e0c473c19..74652f405 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -620,20 +620,20 @@ impl Response { info: crate::WidgetInfo, ) { use crate::WidgetType; - use accesskit::{CheckedState, Role}; + use accesskit::{Checked, Role}; self.fill_accesskit_node_common(builder); builder.set_role(match info.typ { WidgetType::Label => Role::StaticText, WidgetType::Link => Role::Link, - WidgetType::TextEdit => Role::TextField, + WidgetType::TextEdit => Role::TextInput, WidgetType::Button | WidgetType::ImageButton | WidgetType::CollapsingHeader => { Role::Button } WidgetType::Checkbox => Role::CheckBox, WidgetType::RadioButton => Role::RadioButton, WidgetType::SelectableLabel => Role::ToggleButton, - WidgetType::ComboBox => Role::PopupButton, + WidgetType::ComboBox => Role::ComboBox, WidgetType::Slider => Role::Slider, WidgetType::DragValue => Role::SpinButton, WidgetType::ColorButton => Role::ColorWell, @@ -649,10 +649,10 @@ impl Response { builder.set_numeric_value(value); } if let Some(selected) = info.selected { - builder.set_checked_state(if selected { - CheckedState::True + builder.set_checked(if selected { + Checked::True } else { - CheckedState::False + Checked::False }); } } diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 7de4733a0..612f04cea 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -1,5 +1,7 @@ use std::sync::Arc; +#[cfg(feature = "accesskit")] +use accesskit::Role; use epaint::text::{cursor::*, Galley, LayoutJob}; use crate::{output::OutputEvent, *}; @@ -751,7 +753,7 @@ impl<'t> TextEdit<'t> { builder.set_default_action_verb(accesskit::DefaultActionVerb::Focus); if self.multiline { - builder.set_multiline(); + builder.set_role(Role::MultilineTextInput); } parent_id @@ -759,7 +761,7 @@ impl<'t> TextEdit<'t> { if let Some(parent_id) = parent_id { // drop ctx lock before further processing - use accesskit::{Role, TextDirection}; + use accesskit::TextDirection; ui.ctx().with_accessibility_parent(parent_id, || { for (i, row) in galley.rows.iter().enumerate() { From 9ee6669f8fa160eaee5ee7568e95bdc425197b19 Mon Sep 17 00:00:00 2001 From: Chris Cate <3527720+chriscate@users.noreply.github.com> Date: Fri, 10 Nov 2023 04:49:05 -0600 Subject: [PATCH 07/19] Fix rounding of `ImageButton` (#3531) * ImageButton rounding fix * remove unnecessary struct creation * added rounding method for ImageButton * grammar fix * simplify the code slightly --------- Co-authored-by: Emil Ernerfeldt --- crates/egui/src/widgets/button.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index 4c94cc9d4..e11a33b19 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -594,6 +594,14 @@ impl<'a> ImageButton<'a> { self.sense = sense; self } + + /// Set rounding for the `ImageButton`. + /// If the underlying image already has rounding, this + /// will override that value. + pub fn rounding(mut self, rounding: impl Into) -> Self { + self.image = self.image.rounding(rounding.into()); + self + } } impl<'a> Widget for ImageButton<'a> { @@ -621,7 +629,7 @@ impl<'a> Widget for ImageButton<'a> { let selection = ui.visuals().selection; ( Vec2::ZERO, - Rounding::ZERO, + self.image.image_options().rounding, selection.bg_fill, selection.stroke, ) @@ -630,7 +638,7 @@ impl<'a> Widget for ImageButton<'a> { let expansion = Vec2::splat(visuals.expansion); ( expansion, - visuals.rounding, + self.image.image_options().rounding, visuals.weak_bg_fill, visuals.bg_stroke, ) @@ -646,10 +654,8 @@ impl<'a> Widget for ImageButton<'a> { .layout() .align_size_within_rect(image_size, rect.shrink2(padding)); // let image_rect = image_rect.expand2(expansion); // can make it blurry, so let's not - let image_options = ImageOptions { - rounding, // apply rounding to the image - ..self.image.image_options().clone() - }; + let image_options = self.image.image_options().clone(); + widgets::image::paint_texture_load_result(ui, &tlr, image_rect, None, &image_options); // Draw frame outline: From 4c74e92911d7fd3b66cd9c673591e5dae1eb2752 Mon Sep 17 00:00:00 2001 From: Bayley Foster <43776524+apekros@users.noreply.github.com> Date: Fri, 10 Nov 2023 22:41:34 +1030 Subject: [PATCH 08/19] docs: fix typo (#3421) --- crates/egui/src/response.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 74652f405..a194a6666 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -479,7 +479,7 @@ impl Response { /// Highlight this widget, to make it look like it is hovered, even if it isn't. /// - /// The highlight takes on frame to take effect if you call this after the widget has been fully rendered. + /// The highlight takes one frame to take effect if you call this after the widget has been fully rendered. /// /// See also [`Context::highlight_widget`]. pub fn highlight(mut self) -> Self { From c0b14f4d4e7ec5a99ed0246e541a5c6a49eea7aa Mon Sep 17 00:00:00 2001 From: Rinde van Lon Date: Fri, 10 Nov 2023 12:11:48 +0000 Subject: [PATCH 09/19] fix-inconsistent-naming (#3438) --- crates/egui/src/containers/area.rs | 2 +- crates/egui/src/containers/window.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 52c02b18b..4f603717e 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -155,7 +155,7 @@ impl Area { self } - /// Constraint the movement of the window to the given rectangle. + /// Constrain the movement of the window to the given rectangle. /// /// For instance: `.constrain_to(ctx.screen_rect())`. pub fn constrain_to(mut self, constrain_rect: Rect) -> Self { diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 6c385bbff..afc9f48d3 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -147,7 +147,7 @@ impl<'open> Window<'open> { /// Constrains this window to the screen bounds. /// - /// To change the area to constrain to, use [`Self::constraint_to`]. + /// To change the area to constrain to, use [`Self::constrain_to`]. /// /// Default: `true`. pub fn constrain(mut self, constrain: bool) -> Self { @@ -155,10 +155,10 @@ impl<'open> Window<'open> { self } - /// Constraint the movement of the window to the given rectangle. + /// Constrain the movement of the window to the given rectangle. /// /// For instance: `.constrain_to(ctx.screen_rect())`. - pub fn constraint_to(mut self, constrain_rect: Rect) -> Self { + pub fn constrain_to(mut self, constrain_rect: Rect) -> Self { self.area = self.area.constrain_to(constrain_rect); self } From 5201c045128cbb3a229a185ce6f9ccad04ff3713 Mon Sep 17 00:00:00 2001 From: LoganDark Date: Fri, 10 Nov 2023 12:36:21 -0800 Subject: [PATCH 10/19] egui: add redo support to Undoer (#3478) * Closes #3447 * Closes #3448 Better implementation than #3448. (by accident since I did not see that PR) --- crates/egui/src/util/undoer.rs | 34 +++++++++++++++++--- crates/egui/src/widgets/text_edit/builder.rs | 22 +++++++++++-- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/crates/egui/src/util/undoer.rs b/crates/egui/src/util/undoer.rs index b5e272aff..de6d27161 100644 --- a/crates/egui/src/util/undoer.rs +++ b/crates/egui/src/util/undoer.rs @@ -57,15 +57,22 @@ pub struct Undoer { /// The latest undo point may (often) be the current state. undos: VecDeque, + /// Stores redos immediately after a sequence of undos. + /// Gets cleared every time the state changes. + /// Does not need to be a deque, because there can only be up to undos.len() redos, + /// which is already limited to settings.max_undos. + redos: Vec, + #[cfg_attr(feature = "serde", serde(skip))] flux: Option>, } impl std::fmt::Debug for Undoer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let Self { undos, .. } = self; + let Self { undos, redos, .. } = self; f.debug_struct("Undoer") .field("undo count", &undos.len()) + .field("redo count", &redos.len()) .finish() } } @@ -91,6 +98,10 @@ where } } + pub fn has_redo(&self, current_state: &State) -> bool { + !self.redos.is_empty() && self.undos.back() == Some(current_state) + } + /// Return true if the state is currently changing pub fn is_in_flux(&self) -> bool { self.flux.is_some() @@ -101,7 +112,9 @@ where self.flux = None; if self.undos.back() == Some(current_state) { - self.undos.pop_back(); + self.redos.push(self.undos.pop_back().unwrap()); + } else { + self.redos.push(current_state.clone()); } // Note: we keep the undo point intact. @@ -111,9 +124,20 @@ where } } + pub fn redo(&mut self, current_state: &State) -> Option<&State> { + if !self.undos.is_empty() && self.undos.back() != Some(current_state) { + // state changed since the last undo, redos should be cleared. + self.redos.clear(); + None + } else if let Some(state) = self.redos.pop() { + self.undos.push_back(state); + self.undos.back() + } else { + None + } + } + /// Add an undo point if, and only if, there has been a change since the latest undo point. - /// - /// * `time`: current time in seconds. pub fn add_undo(&mut self, current_state: &State) { if self.undos.back() != Some(current_state) { self.undos.push_back(current_state.clone()); @@ -139,6 +163,8 @@ where if latest_undo == current_state { self.flux = None; } else { + self.redos.clear(); + match self.flux.as_mut() { None => { self.flux = Some(Flux { diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 612f04cea..7898c2256 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -986,8 +986,7 @@ fn events( pressed: true, modifiers, .. - } if modifiers.command && !modifiers.shift => { - // TODO(emilk): redo + } if modifiers.matches(Modifiers::COMMAND) => { if let Some((undo_ccursor_range, undo_txt)) = state .undoer .lock() @@ -999,6 +998,25 @@ fn events( None } } + Event::Key { + key, + pressed: true, + modifiers, + .. + } if (modifiers.matches(Modifiers::COMMAND) && *key == Key::Y) + || (modifiers.matches(Modifiers::SHIFT | Modifiers::COMMAND) && *key == Key::Z) => + { + if let Some((redo_ccursor_range, redo_txt)) = state + .undoer + .lock() + .redo(&(cursor_range.as_ccursor_range(), text.as_str().to_owned())) + { + text.replace(redo_txt); + Some(*redo_ccursor_range) + } else { + None + } + } Event::Key { key, From 5f4046d68aa482167198d5fa3b8f506f04242014 Mon Sep 17 00:00:00 2001 From: Phen-Ro Date: Fri, 10 Nov 2023 15:36:51 -0500 Subject: [PATCH 11/19] Use `impl Into` as argument in a few more places (#3420) * Functions that take Stroke were updated to take Into to make them consistent with other Into parameters. * Vec2 implements DivAssign, to make it consistent with already implementing MulAssign and Div. * Vec2::angled() uses sin_cos() rather than an individual sin() and cos() call for an immeasurable but hypothetical performance improvement. * Disable the lock_reentry_single_thread() mutex test. Lock()ing twice on the same thread is not guaranteed to panic. * Closes . --- crates/egui/src/containers/frame.rs | 4 ++-- crates/egui/src/containers/resize.rs | 8 +++++++- crates/egui/src/containers/window.rs | 7 ++++++- crates/egui/src/painter.rs | 3 ++- crates/emath/src/vec2.rs | 29 ++++++++++++++++++++++++++-- crates/epaint/src/mutex.rs | 8 -------- 6 files changed, 44 insertions(+), 15 deletions(-) diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index 6ce5cbc40..e92269751 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -127,8 +127,8 @@ impl Frame { } #[inline] - pub fn stroke(mut self, stroke: Stroke) -> Self { - self.stroke = stroke; + pub fn stroke(mut self, stroke: impl Into) -> Self { + self.stroke = stroke.into(); self } diff --git a/crates/egui/src/containers/resize.rs b/crates/egui/src/containers/resize.rs index 789172342..7274b6c5a 100644 --- a/crates/egui/src/containers/resize.rs +++ b/crates/egui/src/containers/resize.rs @@ -337,10 +337,16 @@ pub fn paint_resize_corner(ui: &Ui, response: &Response) { paint_resize_corner_with_style(ui, &response.rect, stroke, Align2::RIGHT_BOTTOM); } -pub fn paint_resize_corner_with_style(ui: &Ui, rect: &Rect, stroke: Stroke, corner: Align2) { +pub fn paint_resize_corner_with_style( + ui: &Ui, + rect: &Rect, + stroke: impl Into, + corner: Align2, +) { let painter = ui.painter(); let cp = painter.round_pos_to_pixels(corner.pos_in_rect(rect)); let mut w = 2.0; + let stroke = stroke.into(); while w <= rect.width() && w <= rect.height() { painter.line_segment( diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index afc9f48d3..af72ff98b 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -476,7 +476,12 @@ impl<'open> Window<'open> { } } -fn paint_resize_corner(ui: &Ui, possible: &PossibleInteractions, outer_rect: Rect, stroke: Stroke) { +fn paint_resize_corner( + ui: &Ui, + possible: &PossibleInteractions, + outer_rect: Rect, + stroke: impl Into, +) { let corner = if possible.resize_right && possible.resize_bottom { Align2::RIGHT_BOTTOM } else if possible.resize_left && possible.resize_bottom { diff --git a/crates/egui/src/painter.rs b/crates/egui/src/painter.rs index 388a1ced1..a88862325 100644 --- a/crates/egui/src/painter.rs +++ b/crates/egui/src/painter.rs @@ -333,12 +333,13 @@ impl Painter { } /// Show an arrow starting at `origin` and going in the direction of `vec`, with the length `vec.length()`. - pub fn arrow(&self, origin: Pos2, vec: Vec2, stroke: Stroke) { + pub fn arrow(&self, origin: Pos2, vec: Vec2, stroke: impl Into) { use crate::emath::*; let rot = Rot2::from_angle(std::f32::consts::TAU / 10.0); let tip_length = vec.length() / 4.0; let tip = origin + vec; let dir = vec.normalized(); + let stroke = stroke.into(); self.line_segment([origin, tip], stroke); self.line_segment([tip, tip - tip_length * (rot * dir)], stroke); self.line_segment([tip, tip - tip_length * (rot.inverse() * dir)], stroke); diff --git a/crates/emath/src/vec2.rs b/crates/emath/src/vec2.rs index 0e0a93735..d4a2c3300 100644 --- a/crates/emath/src/vec2.rs +++ b/crates/emath/src/vec2.rs @@ -1,4 +1,4 @@ -use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign}; +use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; /// A vector has a direction and length. /// A [`Vec2`] is often used to represent a size. @@ -203,7 +203,8 @@ impl Vec2 { /// ``` #[inline(always)] pub fn angled(angle: f32) -> Self { - vec2(angle.cos(), angle.sin()) + let (sin, cos) = angle.sin_cos(); + vec2(cos, sin) } #[must_use] @@ -407,6 +408,14 @@ impl MulAssign for Vec2 { } } +impl DivAssign for Vec2 { + #[inline(always)] + fn div_assign(&mut self, rhs: f32) { + self.x /= rhs; + self.y /= rhs; + } +} + impl Mul for Vec2 { type Output = Vec2; @@ -470,4 +479,20 @@ fn test_vec2() { assert_eq!(Vec2::DOWN.angle(), 0.25 * TAU); almost_eq!(Vec2::LEFT.angle(), 0.50 * TAU); assert_eq!(Vec2::UP.angle(), -0.25 * TAU); + + let mut assignment = vec2(1.0, 2.0); + assignment += vec2(3.0, 4.0); + assert_eq!(assignment, vec2(4.0, 6.0)); + + let mut assignment = vec2(4.0, 6.0); + assignment -= vec2(1.0, 2.0); + assert_eq!(assignment, vec2(3.0, 4.0)); + + let mut assignment = vec2(1.0, 2.0); + assignment *= 2.0; + assert_eq!(assignment, vec2(2.0, 4.0)); + + let mut assignment = vec2(2.0, 4.0); + assignment /= 2.0; + assert_eq!(assignment, vec2(1.0, 2.0)); } diff --git a/crates/epaint/src/mutex.rs b/crates/epaint/src/mutex.rs index 73b54ca89..2c61f038f 100644 --- a/crates/epaint/src/mutex.rs +++ b/crates/epaint/src/mutex.rs @@ -387,14 +387,6 @@ mod tests { let _b = two.lock(); } - #[test] - #[should_panic] - fn lock_reentry_single_thread() { - let one = Mutex::new(()); - let _a = one.lock(); - let _a2 = one.lock(); // panics - } - #[test] fn lock_multiple_threads() { use std::sync::Arc; From 59b4eff83d14e32ae5bd3a7d294f69e77c478189 Mon Sep 17 00:00:00 2001 From: YgorSouza <43298013+YgorSouza@users.noreply.github.com> Date: Fri, 10 Nov 2023 21:39:49 +0100 Subject: [PATCH 12/19] Fix broken doc links in the demo app widget gallery (#3441) * Closes --- .../egui_demo_lib/src/demo/widget_gallery.rs | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/crates/egui_demo_lib/src/demo/widget_gallery.rs b/crates/egui_demo_lib/src/demo/widget_gallery.rs index 82589c402..4189e61c9 100644 --- a/crates/egui_demo_lib/src/demo/widget_gallery.rs +++ b/crates/egui_demo_lib/src/demo/widget_gallery.rs @@ -109,7 +109,7 @@ impl WidgetGallery { date, } = self; - ui.add(doc_link_label("Label", "label,heading")); + ui.add(doc_link_label("Label", "label")); ui.label("Welcome to the widget gallery!"); ui.end_row(); @@ -121,7 +121,7 @@ impl WidgetGallery { ); ui.end_row(); - ui.add(doc_link_label("TextEdit", "TextEdit,text_edit")); + ui.add(doc_link_label("TextEdit", "TextEdit")); ui.add(egui::TextEdit::singleline(string).hint_text("Write something here")); ui.end_row(); @@ -149,10 +149,7 @@ impl WidgetGallery { }); ui.end_row(); - ui.add(doc_link_label( - "SelectableLabel", - "selectable_value,SelectableLabel", - )); + ui.add(doc_link_label("SelectableLabel", "SelectableLabel")); ui.horizontal(|ui| { ui.selectable_value(radio, Enum::First, "First"); ui.selectable_value(radio, Enum::Second, "Second"); @@ -216,7 +213,11 @@ impl WidgetGallery { #[cfg(feature = "chrono")] { let date = date.get_or_insert_with(|| chrono::offset::Utc::now().date_naive()); - ui.add(doc_link_label("DatePickerButton", "DatePickerButton")); + ui.add(doc_link_label_with_crate( + "egui_extras", + "DatePickerButton", + "DatePickerButton", + )); ui.add(egui_extras::DatePickerButton::new(date)); ui.end_row(); } @@ -237,7 +238,7 @@ impl WidgetGallery { }); ui.end_row(); - ui.add(doc_link_label("Plot", "plot")); + ui.add(doc_link_label_with_crate("egui_plot", "Plot", "plot")); example_plot(ui); ui.end_row(); @@ -273,8 +274,16 @@ fn example_plot(ui: &mut egui::Ui) -> egui::Response { } fn doc_link_label<'a>(title: &'a str, search_term: &'a str) -> impl egui::Widget + 'a { + doc_link_label_with_crate("egui", title, search_term) +} + +fn doc_link_label_with_crate<'a>( + crate_name: &'a str, + title: &'a str, + search_term: &'a str, +) -> impl egui::Widget + 'a { let label = format!("{title}:"); - let url = format!("https://docs.rs/egui?search={search_term}"); + let url = format!("https://docs.rs/{crate_name}?search={search_term}"); move |ui: &mut egui::Ui| { ui.hyperlink_to(label, url).on_hover_ui(|ui| { ui.horizontal_wrapped(|ui| { From 85e14e89bd08654b03c318f18e6a98410e5f315a Mon Sep 17 00:00:00 2001 From: Arnold Loubriat Date: Sat, 11 Nov 2023 09:16:38 +0100 Subject: [PATCH 13/19] Fix Shift+Tab behavior when no widget is focused (#3498) If no widget is focused (such as when an application just started or after Escape was pressed), pressing Shift+Tab does not set the focus to the last widget. I think this PR fixes that. --- crates/egui/src/memory.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index 2c56d9672..908d9f7bb 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -415,6 +415,13 @@ impl Focus { // nothing has focus and the user pressed tab - give focus to the first widgets that wants it: self.focused_widget = Some(FocusWidget::new(id)); self.reset_focus(); + } else if self.focus_direction == FocusDirection::Previous + && self.focused_widget.is_none() + && !self.give_to_next + { + // nothing has focus and the user pressed Shift+Tab - give focus to the last widgets that wants it: + self.focused_widget = self.last_interested.map(FocusWidget::new); + self.reset_focus(); } self.last_interested = Some(id); From 33a034c42f2afbbec384c38eff6be6c525744f05 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 11 Nov 2023 18:32:00 +0100 Subject: [PATCH 14/19] Bump rustix from 0.37.23 to 0.37.25 (#3487) Bumps [rustix](https://github.com/bytecodealliance/rustix) from 0.37.23 to 0.37.25.
Commits
  • 00b84d6 chore: Release rustix version 0.37.25
  • cad15a7 Fixes for Dir on macOS, FreeBSD, and WASI.
  • df3c3a1 Merge pull request from GHSA-c827-hfw6-qwvm
  • b78aeff chore: Release rustix version 0.37.24
  • c0c3f01 Add GNU/Hurd support (#852)
  • f416b6b Fix the test_ttyname_ok test when /dev/stdin is inaccessable. (#821)
  • aee5b09 Downgrade dependencies and disable tests to compile under Rust 1.48.
  • 6d42c38 Disable MIPS in CI. (#793)
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rustix&package-manager=cargo&previous-version=0.37.23&new-version=0.37.25)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/emilk/egui/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1500d9e29..13ace058d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -286,7 +286,7 @@ dependencies = [ "log", "parking", "polling", - "rustix 0.37.23", + "rustix 0.37.25", "slab", "socket2", "waker-fn", @@ -3287,9 +3287,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.37.23" +version = "0.37.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +checksum = "d4eb579851244c2c03e7c24f501c3432bed80b8f720af1d6e5b0e0f01555a035" dependencies = [ "bitflags 1.3.2", "errno", From 6a785d4b4790988c5c4d7ba26b82550f6fff8399 Mon Sep 17 00:00:00 2001 From: Francisco Ayala Le Brun Date: Sat, 11 Nov 2023 18:35:57 +0100 Subject: [PATCH 15/19] Taking over egui_glium (#3535) Hello. I would like to maintain egui_glium. I have already updated my fork to the newest version of glium. You can find it at: https://github.com/fayalalebrun/egui_glium Let me know about next steps regarding access to crates.io. --- ARCHITECTURE.md | 5 +- CHANGELOG.md | 2 +- README.md | 3 +- crates/egui_glium/CHANGELOG.md | 125 ------ crates/egui_glium/Cargo.toml | 58 --- crates/egui_glium/README.md | 21 - crates/egui_glium/examples/native_texture.rs | 138 ------- crates/egui_glium/examples/pure_glium.rs | 108 ----- crates/egui_glium/src/lib.rs | 100 ----- crates/egui_glium/src/painter.rs | 381 ------------------ .../egui_glium/src/shader/fragment_100es.glsl | 38 -- .../egui_glium/src/shader/fragment_120.glsl | 31 -- .../egui_glium/src/shader/fragment_140.glsl | 32 -- .../egui_glium/src/shader/fragment_300es.glsl | 32 -- .../egui_glium/src/shader/vertex_100es.glsl | 19 - crates/egui_glium/src/shader/vertex_120.glsl | 18 - crates/egui_glium/src/shader/vertex_140.glsl | 18 - .../egui_glium/src/shader/vertex_300es.glsl | 19 - 18 files changed, 3 insertions(+), 1145 deletions(-) delete mode 100644 crates/egui_glium/CHANGELOG.md delete mode 100644 crates/egui_glium/Cargo.toml delete mode 100644 crates/egui_glium/README.md delete mode 100644 crates/egui_glium/examples/native_texture.rs delete mode 100644 crates/egui_glium/examples/pure_glium.rs delete mode 100644 crates/egui_glium/src/lib.rs delete mode 100644 crates/egui_glium/src/painter.rs delete mode 100644 crates/egui_glium/src/shader/fragment_100es.glsl delete mode 100644 crates/egui_glium/src/shader/fragment_120.glsl delete mode 100644 crates/egui_glium/src/shader/fragment_140.glsl delete mode 100644 crates/egui_glium/src/shader/fragment_300es.glsl delete mode 100644 crates/egui_glium/src/shader/vertex_100es.glsl delete mode 100644 crates/egui_glium/src/shader/vertex_120.glsl delete mode 100644 crates/egui_glium/src/shader/vertex_140.glsl delete mode 100644 crates/egui_glium/src/shader/vertex_300es.glsl diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index d3f6e10e7..c9f314f7d 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -5,7 +5,7 @@ Also see [`CONTRIBUTING.md`](CONTRIBUTING.md) for what to do before opening a PR ## Crate overview -The crates in this repository are: `egui, emath, epaint, egui_extras, egui_plot, egui-winit, egui_glium, egui_glow, egui_demo_lib, egui_demo_app`. +The crates in this repository are: `egui, emath, epaint, egui_extras, egui_plot, egui-winit, egui_glow, egui_demo_lib, egui_demo_app`. ### `egui`: The main GUI library. Example code: `if ui.button("Click me").clicked() { … }` @@ -32,9 +32,6 @@ This crates provides bindings between [`egui`](https://github.com/emilk/egui) an The library translates winit events to egui, handled copy/paste, updates the cursor, open links clicked in egui, etc. -### `egui_glium` -Puts an egui app inside a native window on your laptop. Paints the triangles that egui outputs using [glium](https://github.com/glium/glium). - ### `egui_glow` Puts an egui app inside a native window on your laptop. Paints the triangles that egui outputs using [glow](https://github.com/grovesNL/glow). diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e4da4186..9e6d04471 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # egui changelog All notable changes to the `egui` crate will be documented in this file. -NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`egui_plot`](crates/egui_plot/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG.md), [`egui-winit`](crates/egui-winit/CHANGELOG.md), [`egui_glium`](crates/egui_glium/CHANGELOG.md), [`egui_glow`](crates/egui_glow/CHANGELOG.md) and [`egui-wgpu`](crates/egui-wgpu/CHANGELOG.md) have their own changelogs! +NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`egui_plot`](crates/egui_plot/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG.md), [`egui-winit`](crates/egui-winit/CHANGELOG.md), [`egui_glow`](crates/egui_glow/CHANGELOG.md) and [`egui-wgpu`](crates/egui-wgpu/CHANGELOG.md) have their own changelogs! This file is updated upon each release. Changes since the last release can be found by running the `scripts/generate_changelog.py` script. diff --git a/README.md b/README.md index 520304b14..59d63e165 100644 --- a/README.md +++ b/README.md @@ -201,13 +201,13 @@ These are the official egui integrations: * [`egui_glow`](https://github.com/emilk/egui/tree/master/crates/egui_glow) for rendering egui with [glow](https://github.com/grovesNL/glow) on native and web, and for making native apps. * [`egui-wgpu`](https://github.com/emilk/egui/tree/master/crates/egui-wgpu) for [wgpu](https://crates.io/crates/wgpu) (WebGPU API). * [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit) for integrating with [winit](https://github.com/rust-windowing/winit). -* [`egui_glium`](https://github.com/emilk/egui/tree/master/crates/egui_glium) for compiling native apps with [Glium](https://github.com/glium/glium) (DEPRECATED - looking for new maintainer). ### 3rd party integrations * [`amethyst_egui`](https://github.com/jgraef/amethyst_egui) for [the Amethyst game engine](https://amethyst.rs/). * [`bevy_egui`](https://github.com/mvlabat/bevy_egui) for [the Bevy game engine](https://bevyengine.org/). * [`egui_glfw_gl`](https://github.com/cohaereo/egui_glfw_gl) for [GLFW](https://crates.io/crates/glfw). +* [`egui_glium`](https://github.com/fayalalebrun/egui_glium) for compiling native apps with [Glium](https://github.com/glium/glium). * [`egui-glutin-gl`](https://github.com/h3r2tic/egui-glutin-gl/) for [glutin](https://crates.io/crates/glutin). * [`egui_sdl2_gl`](https://crates.io/crates/egui_sdl2_gl) for [SDL2](https://crates.io/crates/sdl2). * [`egui_sdl2_platform`](https://github.com/ComLarsic/egui_sdl2_platform) for [SDL2](https://crates.io/crates/sdl2). @@ -341,7 +341,6 @@ You can also render your 3D scene to a texture and display it using [`ui.image( Examples: * Using [`egui-miniquad`]( https://github.com/not-fl3/egui-miniquad): https://github.com/not-fl3/egui-miniquad/blob/master/examples/render_to_egui_image.rs -* Using [`egui_glium`](https://github.com/emilk/egui/tree/master/crates/egui_glium): . ## Other diff --git a/crates/egui_glium/CHANGELOG.md b/crates/egui_glium/CHANGELOG.md deleted file mode 100644 index c1e98201d..000000000 --- a/crates/egui_glium/CHANGELOG.md +++ /dev/null @@ -1,125 +0,0 @@ -# Changelog for egui_glium -All notable changes to the `egui_glium` integration will be noted in this file. - -This file is updated upon each release. -Changes since the last release can be found by running the `scripts/generate_changelog.py` script. - - -## Unreleased -* Remove the `screen_reader` feature ([#2669](https://github.com/emilk/egui/pull/2669)). - - -## 0.20.1 - 2022-12-11 -* Fix [docs.rs](https://docs.rs/egui_glium) build ([#2420](https://github.com/emilk/egui/pull/2420)). - - -## 0.20.0 - 2022-12-08 -* MSRV (Minimum Supported Rust Version) is now `1.65.0` ([#2314](https://github.com/emilk/egui/pull/2314)). - - -## 0.19.0 - 2022-08-20 -* MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)). - - -## 0.18.0 - 2022-04-30 -* Remove "epi" feature ([#1361](https://github.com/emilk/egui/pull/1361)). -* Remove need for `trait epi::NativeTexture` to use the `fn register_native_texture/replace_native_texture` ([#1361](https://github.com/emilk/egui/pull/1361)). -* MSRV (Minimum Supported Rust Version) is now `1.60.0` ([#1467](https://github.com/emilk/egui/pull/1467)). - - -## 0.17.0 - 2022-02-22 -* `EguiGlium::run` no longer returns the shapes to paint, but stores them internally until you call `EguiGlium::paint` ([#1110](https://github.com/emilk/egui/pull/1110)). -* Optimize the painter and texture uploading ([#1110](https://github.com/emilk/egui/pull/1110)). -* Automatically detect and apply dark or light mode from system ([#1045](https://github.com/emilk/egui/pull/1045)). - - -## 0.16.0 - 2021-12-29 -* Simplified `EguiGlium` interface ([#871](https://github.com/emilk/egui/pull/871)). -* Removed `EguiGlium::is_quit_event` ([#881](https://github.com/emilk/egui/pull/881)). -* Updated `glium` to 0.31 ([#930](https://github.com/emilk/egui/pull/930)). -* Changed the `Painter` interface slightly ([#999](https://github.com/emilk/egui/pull/999)). - - -## 0.15.0 - 2021-10-24 -* Remove "http" feature (use https://github.com/emilk/ehttp instead!). -* Implement `epi::NativeTexture` trait for the glium painter. -* Deprecate 'Painter::register_glium_texture'. -* Increase scroll speed. -* Restore window position on startup without flickering. -* A lot of the code has been moved to the new library [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit). -* Fixed reactive mode on windows. - - -## 0.14.0 - 2021-08-24 -* Fixed native file dialogs hanging (eg. when using [`rfd`](https://github.com/PolyMeilex/rfd)). -* Implement drag-and-dropping files into the application. -* [Fix minimize on Windows](https://github.com/emilk/egui/issues/518). -* Change `drag_and_drop_support` to `false` by default (Windows only). See . -* Don't restore window position on Windows, because the position would sometimes be invalid. - - -## 0.13.1 - 2021-06-24 -* Fixed `http` feature flag and docs - - -## 0.13.0 - 2021-06-24 -* Added `EguiGlium::is_quit_event` to replace `control_flow` argument to `EguiGlium::on_event`. -* [Fix modifier key for zoom with mouse wheel on Mac](https://github.com/emilk/egui/issues/401) -* [Fix stuck modifier keys](https://github.com/emilk/egui/pull/479) - - -## 0.12.0 - 2021-05-10 -* Simplify usage with a new `EguiGlium` wrapper type. - - -## 0.11.0 - 2021-04-05 -* [Position IME candidate window next to text cursor](https://github.com/emilk/egui/pull/258). -* [Register your own glium textures](https://github.com/emilk/egui/pull/226). -* [Fix cursor icon flickering on Windows(https://github.com/emilk/egui/pull/218). - - -## 0.10.0 - 2021-02-28 -* [Add shaders for GLSL 1.2, GLSL ES 1.0 and 3.0](https://github.com/emilk/egui/pull/187) - - now `egui` works well on old hardware which supports OpenGL 2.1 only like Raspberry Pi 1 and Zero. - - -## 0.9.0 - 2021-02-07 -* Nothing new - - -## 0.8.0 - 2021-01-17 -* Fixed a bug where key releases weren't sent to egui -* Fixed `set_window_size` for non-native `pixels_per_point`. - - -## 0.7.0 - 2021-01-04 -* `http` `persistence` and `time` are now optional (and opt-in) features. - - -## 0.6.0 - 2020-12-26 -### Added ⭐ -* `egui_glium` will auto-save your app state every 30 seconds. -* `egui_glium` can now set windows as fixed size (e.g. the user can't resize the window). See `egui::App::is_resizable()`. - -### Changed 🔧 -* `egui_glium` will now save you app state to [a better directory](https://docs.rs/directories-next/2.0.0/directories_next/struct.ProjectDirs.html#method.data_dir). -* `egui_glium::run`: the parameter `app` now has signature `Box` (you need to add `Box::new(app)` to your code). -* Window title is now passed via the `trait` function `egui::App::name()`. - -### Fixed 🐛 -* Serialize window size in logical points instead of physical pixels. -* Window position is now restored on restart. - - -## 0.5.0 - 2020-12-13 -* FileStorage::from_path now takes `Into` instead of `String` - - -## 0.4.0 - 2020-11-28 -Started changelog. Features: - -* Input -* Painting -* Clipboard handling -* Open URL:s -* Simple JSON-backed storage diff --git a/crates/egui_glium/Cargo.toml b/crates/egui_glium/Cargo.toml deleted file mode 100644 index a76cde4e3..000000000 --- a/crates/egui_glium/Cargo.toml +++ /dev/null @@ -1,58 +0,0 @@ -[package] -name = "egui_glium" -version = "0.23.0" -authors = ["Emil Ernerfeldt "] -description = "Bindings for using egui natively using the glium library" -edition = "2021" -rust-version = "1.70" -homepage = "https://github.com/emilk/egui/tree/master/crates/egui_glium" -license = "MIT OR Apache-2.0" -readme = "README.md" -repository = "https://github.com/emilk/egui/tree/master/crates/egui_glium" -categories = ["gui", "game-development"] -keywords = ["glium", "egui", "gui", "gamedev"] -include = [ - "../LICENSE-APACHE", - "../LICENSE-MIT", - "**/*.rs", - "Cargo.toml", - "src/shader/*.glsl", -] - -[package.metadata.docs.rs] -all-features = true - - -[features] -default = ["clipboard", "links"] - -## Enable cut/copy/paste to OS clipboard. -## -## If disabled a clipboard will be simulated so you can still copy/paste within the egui app. -clipboard = ["egui-winit/clipboard"] - -## Enable opening links in a browser when an egui hyperlink is clicked. -links = ["egui-winit/links"] - - -[dependencies] -egui = { version = "0.23.0", path = "../egui", default-features = false, features = [ - "bytemuck", -] } -egui-winit = { version = "0.23.0", path = "../egui-winit", default-features = false } - -ahash = { version = "0.8.1", default-features = false, features = [ - "no-rng", # we don't need DOS-protection, so we let users opt-in to it instead - "std", -] } -bytemuck = "1.7" -glium = "0.32" - -#! ### Optional dependencies -## Enable this when generating docs. -document-features = { version = "0.2", optional = true } - - -[dev-dependencies] -egui_demo_lib = { version = "0.23.0", path = "../egui_demo_lib", default-features = false } -image = { version = "0.24", default-features = false, features = ["png"] } diff --git a/crates/egui_glium/README.md b/crates/egui_glium/README.md deleted file mode 100644 index 1c31de5fa..000000000 --- a/crates/egui_glium/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# egui_glium - -[![Latest version](https://img.shields.io/crates/v/egui_glium.svg)](https://crates.io/crates/egui_glium) -[![Documentation](https://docs.rs/egui_glium/badge.svg)](https://docs.rs/egui_glium) -[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) -![MIT](https://img.shields.io/badge/license-MIT-blue.svg) -![Apache](https://img.shields.io/badge/license-Apache-blue.svg) - -This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [glium](https://crates.io/crates/glium) which allows you to write GUI code using egui and compile it and run it natively, cross platform. - -To use on Linux, first run: - -``` -sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev -``` - -This crate depends on [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit). - - -## DEPRECATED - Looking for new maintainer -This crate is no longer being updated. If you are interested in keeping `egui_glium` updated, then fork it to its own repository, make a PR to the egui repo removing it, and then I will give you access to it on crates.io so you can publish new `egui_glium` crates. diff --git a/crates/egui_glium/examples/native_texture.rs b/crates/egui_glium/examples/native_texture.rs deleted file mode 100644 index eb5a956f4..000000000 --- a/crates/egui_glium/examples/native_texture.rs +++ /dev/null @@ -1,138 +0,0 @@ -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release - -use glium::glutin; - -fn main() { - let event_loop = glutin::event_loop::EventLoopBuilder::with_user_event().build(); - let display = create_display(&event_loop); - - let mut egui_glium = egui_glium::EguiGlium::new(&display, &event_loop); - - let png_data = include_bytes!("../../../examples/retained_image/src/crab.png"); - let image = load_glium_image(png_data); - let image_size = egui::vec2(image.width as f32, image.height as f32); - // Load to gpu memory - let glium_texture = glium::texture::SrgbTexture2d::new(&display, image).unwrap(); - // Allow us to share the texture with egui: - let glium_texture = std::rc::Rc::new(glium_texture); - // Allocate egui's texture id for GL texture - let texture_id = egui_glium - .painter - .register_native_texture(glium_texture, Default::default()); - // Setup button image size for reasonable image size for button container. - let button_image_size = egui::vec2(32_f32, 32_f32); - - event_loop.run(move |event, _, control_flow| { - let mut redraw = || { - let mut quit = false; - - let repaint_after = egui_glium.run(&display, |egui_ctx| { - egui::SidePanel::left("my_side_panel").show(egui_ctx, |ui| { - if ui - .add(egui::Button::image_and_text( - (texture_id, button_image_size), - "Quit", - )) - .clicked() - { - quit = true; - } - }); - egui::Window::new("NativeTextureDisplay").show(egui_ctx, |ui| { - ui.image(texture_id, image_size); - }); - }); - - *control_flow = if quit { - glutin::event_loop::ControlFlow::Exit - } else if repaint_after.is_zero() { - display.gl_window().window().request_redraw(); - glutin::event_loop::ControlFlow::Poll - } else if let Some(repaint_after_instant) = - std::time::Instant::now().checked_add(repaint_after) - { - glutin::event_loop::ControlFlow::WaitUntil(repaint_after_instant) - } else { - glutin::event_loop::ControlFlow::Wait - }; - - { - use glium::Surface as _; - let mut target = display.draw(); - - let color = egui::Rgba::from_rgb(0.1, 0.3, 0.2); - target.clear_color(color[0], color[1], color[2], color[3]); - - // draw things behind egui here - - egui_glium.paint(&display, &mut target); - - // draw things on top of egui here - - target.finish().unwrap(); - } - }; - - match event { - // Platform-dependent event handlers to workaround a winit bug - // See: https://github.com/rust-windowing/winit/issues/987 - // See: https://github.com/rust-windowing/winit/issues/1619 - glutin::event::Event::RedrawEventsCleared if cfg!(target_os = "windows") => redraw(), - glutin::event::Event::RedrawRequested(_) if !cfg!(target_os = "windows") => redraw(), - - glutin::event::Event::WindowEvent { event, .. } => { - use glutin::event::WindowEvent; - if matches!(event, WindowEvent::CloseRequested | WindowEvent::Destroyed) { - *control_flow = glutin::event_loop::ControlFlow::Exit; - } - - let event_response = egui_glium.on_event(&event); - - if event_response.repaint { - display.gl_window().window().request_redraw(); - } - } - glutin::event::Event::NewEvents(glutin::event::StartCause::ResumeTimeReached { - .. - }) => { - display.gl_window().window().request_redraw(); - } - - _ => (), - } - }); -} - -fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display { - let window_builder = glutin::window::WindowBuilder::new() - .with_resizable(true) - .with_inner_size(glutin::dpi::LogicalSize { - width: 800.0, - height: 600.0, - }) - .with_title("egui_glium example"); - - let context_builder = glutin::ContextBuilder::new() - .with_depth_buffer(0) - .with_stencil_buffer(0) - .with_vsync(true); - - glium::Display::new(window_builder, context_builder, event_loop).unwrap() -} - -fn load_glium_image(png_data: &[u8]) -> glium::texture::RawImage2d<'_, u8> { - // Load image using the image crate: - let image = image::load_from_memory(png_data).unwrap().to_rgba8(); - let image_dimensions = image.dimensions(); - - // Premultiply alpha: - let pixels: Vec<_> = image - .into_vec() - .chunks_exact(4) - .map(|p| egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3])) - .flat_map(|color| color.to_array()) - .collect(); - - // Convert to glium image: - glium::texture::RawImage2d::from_raw_rgba(pixels, image_dimensions) -} diff --git a/crates/egui_glium/examples/pure_glium.rs b/crates/egui_glium/examples/pure_glium.rs deleted file mode 100644 index 801f7b145..000000000 --- a/crates/egui_glium/examples/pure_glium.rs +++ /dev/null @@ -1,108 +0,0 @@ -//! Example how to use `egui_glium`. - -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release - -use glium::glutin; - -fn main() { - let event_loop = glutin::event_loop::EventLoopBuilder::with_user_event().build(); - let display = create_display(&event_loop); - - let mut egui_glium = egui_glium::EguiGlium::new(&display, &event_loop); - - let mut color_test = egui_demo_lib::ColorTest::default(); - - event_loop.run(move |event, _, control_flow| { - let mut redraw = || { - let mut quit = false; - - let repaint_after = egui_glium.run(&display, |egui_ctx| { - egui::SidePanel::left("my_side_panel").show(egui_ctx, |ui| { - ui.heading("Hello World!"); - if ui.button("Quit").clicked() { - quit = true; - } - }); - - egui::CentralPanel::default().show(egui_ctx, |ui| { - egui::ScrollArea::vertical().show(ui, |ui| { - color_test.ui(ui); - }); - }); - }); - - *control_flow = if quit { - glutin::event_loop::ControlFlow::Exit - } else if repaint_after.is_zero() { - display.gl_window().window().request_redraw(); - glutin::event_loop::ControlFlow::Poll - } else if let Some(repaint_after_instant) = - std::time::Instant::now().checked_add(repaint_after) - { - glutin::event_loop::ControlFlow::WaitUntil(repaint_after_instant) - } else { - glutin::event_loop::ControlFlow::Wait - }; - - { - use glium::Surface as _; - let mut target = display.draw(); - - let color = egui::Rgba::from_rgb(0.1, 0.3, 0.2); - target.clear_color(color[0], color[1], color[2], color[3]); - - // draw things behind egui here - - egui_glium.paint(&display, &mut target); - - // draw things on top of egui here - - target.finish().unwrap(); - } - }; - - match event { - // Platform-dependent event handlers to workaround a winit bug - // See: https://github.com/rust-windowing/winit/issues/987 - // See: https://github.com/rust-windowing/winit/issues/1619 - glutin::event::Event::RedrawEventsCleared if cfg!(target_os = "windows") => redraw(), - glutin::event::Event::RedrawRequested(_) if !cfg!(target_os = "windows") => redraw(), - - glutin::event::Event::WindowEvent { event, .. } => { - use glutin::event::WindowEvent; - if matches!(event, WindowEvent::CloseRequested | WindowEvent::Destroyed) { - *control_flow = glutin::event_loop::ControlFlow::Exit; - } - - let event_response = egui_glium.on_event(&event); - - if event_response.repaint { - display.gl_window().window().request_redraw(); - } - } - glutin::event::Event::NewEvents(glutin::event::StartCause::ResumeTimeReached { - .. - }) => { - display.gl_window().window().request_redraw(); - } - _ => (), - } - }); -} - -fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display { - let window_builder = glutin::window::WindowBuilder::new() - .with_resizable(true) - .with_inner_size(glutin::dpi::LogicalSize { - width: 800.0, - height: 600.0, - }) - .with_title("egui_glium example"); - - let context_builder = glutin::ContextBuilder::new() - .with_depth_buffer(0) - .with_stencil_buffer(0) - .with_vsync(true); - - glium::Display::new(window_builder, context_builder, event_loop).unwrap() -} diff --git a/crates/egui_glium/src/lib.rs b/crates/egui_glium/src/lib.rs deleted file mode 100644 index da60637dc..000000000 --- a/crates/egui_glium/src/lib.rs +++ /dev/null @@ -1,100 +0,0 @@ -//! [`egui`] bindings for [`glium`](https://github.com/glium/glium). -//! -//! The main type you want to use is [`EguiGlium`]. -//! -//! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead. -//! -//! ## Feature flags -#![cfg_attr(feature = "document-features", doc = document_features::document_features!())] -//! - -#![allow(clippy::float_cmp)] -#![allow(clippy::manual_range_contains)] -#![forbid(unsafe_code)] - -mod painter; -pub use painter::Painter; - -pub use egui_winit; - -use egui_winit::winit::event_loop::EventLoopWindowTarget; -pub use egui_winit::EventResponse; - -// ---------------------------------------------------------------------------- - -/// Convenience wrapper for using [`egui`] from a [`glium`] app. -pub struct EguiGlium { - pub egui_ctx: egui::Context, - pub egui_winit: egui_winit::State, - pub painter: crate::Painter, - - shapes: Vec, - textures_delta: egui::TexturesDelta, -} - -impl EguiGlium { - pub fn new(display: &glium::Display, event_loop: &EventLoopWindowTarget) -> Self { - let painter = crate::Painter::new(display); - - let mut egui_winit = egui_winit::State::new(event_loop); - egui_winit.set_max_texture_side(painter.max_texture_side()); - let pixels_per_point = display.gl_window().window().scale_factor() as f32; - egui_winit.set_pixels_per_point(pixels_per_point); - - Self { - egui_ctx: Default::default(), - egui_winit, - painter, - shapes: Default::default(), - textures_delta: Default::default(), - } - } - - pub fn on_event(&mut self, event: &glium::glutin::event::WindowEvent<'_>) -> EventResponse { - self.egui_winit.on_event(&self.egui_ctx, event) - } - - /// Returns `true` if egui requests a repaint. - /// - /// Call [`Self::paint`] later to paint. - pub fn run( - &mut self, - display: &glium::Display, - run_ui: impl FnMut(&egui::Context), - ) -> std::time::Duration { - let raw_input = self - .egui_winit - .take_egui_input(display.gl_window().window()); - let egui::FullOutput { - platform_output, - repaint_after, - textures_delta, - shapes, - } = self.egui_ctx.run(raw_input, run_ui); - - self.egui_winit.handle_platform_output( - display.gl_window().window(), - &self.egui_ctx, - platform_output, - ); - - self.shapes = shapes; - self.textures_delta.append(textures_delta); - - repaint_after - } - - /// Paint the results of the last call to [`Self::run`]. - pub fn paint(&mut self, display: &glium::Display, target: &mut T) { - let shapes = std::mem::take(&mut self.shapes); - let textures_delta = std::mem::take(&mut self.textures_delta); - let clipped_primitives = self.egui_ctx.tessellate(shapes); - self.painter.paint_and_update_textures( - display, - target, - self.egui_ctx.pixels_per_point(), - &clipped_primitives, - &textures_delta, - ); - } -} diff --git a/crates/egui_glium/src/painter.rs b/crates/egui_glium/src/painter.rs deleted file mode 100644 index 39da163b5..000000000 --- a/crates/egui_glium/src/painter.rs +++ /dev/null @@ -1,381 +0,0 @@ -#![allow(deprecated)] // legacy implement_vertex macro -#![allow(semicolon_in_expressions_from_macros)] // glium::program! macro - -use egui::{ - epaint::{textures::TextureFilter, Primitive}, - TextureOptions, -}; - -use { - egui::{emath::Rect, epaint::Mesh}, - glium::{ - implement_vertex, - index::PrimitiveType, - texture::{self, srgb_texture2d::SrgbTexture2d}, - uniform, - uniforms::{MagnifySamplerFilter, MinifySamplerFilter, SamplerWrapFunction}, - }, - std::rc::Rc, -}; - -pub struct Painter { - max_texture_side: usize, - program: glium::Program, - - textures: ahash::HashMap, - - /// [`egui::TextureId::User`] index - next_native_tex_id: u64, -} - -fn create_program( - facade: &dyn glium::backend::Facade, - vertex_shader: &str, - fragment_shader: &str, -) -> glium::program::Program { - let input = glium::program::ProgramCreationInput::SourceCode { - vertex_shader, - tessellation_control_shader: None, - tessellation_evaluation_shader: None, - geometry_shader: None, - fragment_shader, - transform_feedback_varyings: None, - outputs_srgb: true, - uses_point_size: false, - }; - - glium::program::Program::new(facade, input) - .unwrap_or_else(|err| panic!("Failed to compile shader: {}", err)) -} - -impl Painter { - pub fn new(facade: &dyn glium::backend::Facade) -> Painter { - use glium::CapabilitiesSource as _; - let max_texture_side = facade.get_capabilities().max_texture_size as _; - - let program = if facade - .get_context() - .is_glsl_version_supported(&glium::Version(glium::Api::Gl, 1, 4)) - { - eprintln!("Using GL 1.4"); - create_program( - facade, - include_str!("shader/vertex_140.glsl"), - include_str!("shader/fragment_140.glsl"), - ) - } else if facade - .get_context() - .is_glsl_version_supported(&glium::Version(glium::Api::Gl, 1, 2)) - { - eprintln!("Using GL 1.2"); - create_program( - facade, - include_str!("shader/vertex_120.glsl"), - include_str!("shader/fragment_120.glsl"), - ) - } else if facade - .get_context() - .is_glsl_version_supported(&glium::Version(glium::Api::GlEs, 3, 0)) - { - eprintln!("Using GL ES 3.0"); - create_program( - facade, - include_str!("shader/vertex_300es.glsl"), - include_str!("shader/fragment_300es.glsl"), - ) - } else if facade - .get_context() - .is_glsl_version_supported(&glium::Version(glium::Api::GlEs, 1, 0)) - { - eprintln!("Using GL ES 1.0"); - create_program( - facade, - include_str!("shader/vertex_100es.glsl"), - include_str!("shader/fragment_100es.glsl"), - ) - } else { - panic!( - "Failed to find a compatible shader for OpenGL version {:?}", - facade.get_version() - ) - }; - - Painter { - max_texture_side, - program, - textures: Default::default(), - next_native_tex_id: 0, - } - } - - pub fn max_texture_side(&self) -> usize { - self.max_texture_side - } - - pub fn paint_and_update_textures( - &mut self, - display: &glium::Display, - target: &mut T, - pixels_per_point: f32, - clipped_primitives: &[egui::ClippedPrimitive], - textures_delta: &egui::TexturesDelta, - ) { - for (id, image_delta) in &textures_delta.set { - self.set_texture(display, *id, image_delta); - } - - self.paint_primitives(display, target, pixels_per_point, clipped_primitives); - - for &id in &textures_delta.free { - self.free_texture(id); - } - } - - /// Main entry-point for painting a frame. - /// You should call `target.clear_color(..)` before - /// and `target.finish()` after this. - pub fn paint_primitives( - &mut self, - display: &glium::Display, - target: &mut T, - pixels_per_point: f32, - clipped_primitives: &[egui::ClippedPrimitive], - ) { - for egui::ClippedPrimitive { - clip_rect, - primitive, - } in clipped_primitives - { - match primitive { - Primitive::Mesh(mesh) => { - self.paint_mesh(target, display, pixels_per_point, clip_rect, mesh); - } - Primitive::Callback(_) => { - panic!("Custom rendering callbacks are not implemented in egui_glium"); - } - } - } - } - - #[inline(never)] // Easier profiling - fn paint_mesh( - &mut self, - target: &mut T, - display: &glium::Display, - pixels_per_point: f32, - clip_rect: &Rect, - mesh: &Mesh, - ) { - debug_assert!(mesh.is_valid()); - - let vertex_buffer = { - #[repr(C)] - #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] - struct Vertex { - a_pos: [f32; 2], - a_tc: [f32; 2], - a_srgba: [u8; 4], - } - implement_vertex!(Vertex, a_pos, a_tc, a_srgba); - - let vertices: &[Vertex] = bytemuck::cast_slice(&mesh.vertices); - - // TODO(emilk): we should probably reuse the [`VertexBuffer`] instead of allocating a new one each frame. - glium::VertexBuffer::new(display, vertices).unwrap() - }; - - // TODO(emilk): we should probably reuse the [`IndexBuffer`] instead of allocating a new one each frame. - let index_buffer = - glium::IndexBuffer::new(display, PrimitiveType::TrianglesList, &mesh.indices).unwrap(); - - let (width_in_pixels, height_in_pixels) = display.get_framebuffer_dimensions(); - let width_in_points = width_in_pixels as f32 / pixels_per_point; - let height_in_points = height_in_pixels as f32 / pixels_per_point; - - if let Some(texture) = self.texture(mesh.texture_id) { - // The texture coordinates for text are so that both nearest and linear should work with the egui font texture. - let mag_filter = match texture.options.magnification { - TextureFilter::Nearest => MagnifySamplerFilter::Nearest, - TextureFilter::Linear => MagnifySamplerFilter::Linear, - }; - let min_filter = match texture.options.minification { - TextureFilter::Nearest => MinifySamplerFilter::Nearest, - TextureFilter::Linear => MinifySamplerFilter::Linear, - }; - - let sampler = texture - .glium_texture - .sampled() - .magnify_filter(mag_filter) - .minify_filter(min_filter) - .wrap_function(SamplerWrapFunction::Clamp); - - let uniforms = uniform! { - u_screen_size: [width_in_points, height_in_points], - u_sampler: sampler, - }; - - // egui outputs colors with premultiplied alpha: - let color_blend_func = glium::BlendingFunction::Addition { - source: glium::LinearBlendingFactor::One, - destination: glium::LinearBlendingFactor::OneMinusSourceAlpha, - }; - - // Less important, but this is technically the correct alpha blend function - // when you want to make use of the framebuffer alpha (for screenshots, compositing, etc). - let alpha_blend_func = glium::BlendingFunction::Addition { - source: glium::LinearBlendingFactor::OneMinusDestinationAlpha, - destination: glium::LinearBlendingFactor::One, - }; - - let blend = glium::Blend { - color: color_blend_func, - alpha: alpha_blend_func, - ..Default::default() - }; - - // egui outputs mesh in both winding orders: - let backface_culling = glium::BackfaceCullingMode::CullingDisabled; - - // Transform clip rect to physical pixels: - let clip_min_x = pixels_per_point * clip_rect.min.x; - let clip_min_y = pixels_per_point * clip_rect.min.y; - let clip_max_x = pixels_per_point * clip_rect.max.x; - let clip_max_y = pixels_per_point * clip_rect.max.y; - - // Make sure clip rect can fit within a `u32`: - let clip_min_x = clip_min_x.clamp(0.0, width_in_pixels as f32); - let clip_min_y = clip_min_y.clamp(0.0, height_in_pixels as f32); - let clip_max_x = clip_max_x.clamp(clip_min_x, width_in_pixels as f32); - let clip_max_y = clip_max_y.clamp(clip_min_y, height_in_pixels as f32); - - let clip_min_x = clip_min_x.round() as u32; - let clip_min_y = clip_min_y.round() as u32; - let clip_max_x = clip_max_x.round() as u32; - let clip_max_y = clip_max_y.round() as u32; - - let params = glium::DrawParameters { - blend, - backface_culling, - scissor: Some(glium::Rect { - left: clip_min_x, - bottom: height_in_pixels - clip_max_y, - width: clip_max_x - clip_min_x, - height: clip_max_y - clip_min_y, - }), - ..Default::default() - }; - - target - .draw( - &vertex_buffer, - &index_buffer, - &self.program, - &uniforms, - ¶ms, - ) - .unwrap(); - } - } - - // ------------------------------------------------------------------------ - - pub fn set_texture( - &mut self, - facade: &dyn glium::backend::Facade, - tex_id: egui::TextureId, - delta: &egui::epaint::ImageDelta, - ) { - let pixels: Vec<(u8, u8, u8, u8)> = match &delta.image { - egui::ImageData::Color(image) => { - assert_eq!( - image.width() * image.height(), - image.pixels.len(), - "Mismatch between texture size and texel count" - ); - image.pixels.iter().map(|color| color.to_tuple()).collect() - } - egui::ImageData::Font(image) => image - .srgba_pixels(None) - .map(|color| color.to_tuple()) - .collect(), - }; - let glium_image = glium::texture::RawImage2d { - data: std::borrow::Cow::Owned(pixels), - width: delta.image.width() as _, - height: delta.image.height() as _, - format: glium::texture::ClientFormat::U8U8U8U8, - }; - let format = texture::SrgbFormat::U8U8U8U8; - let mipmaps = texture::MipmapsOption::NoMipmap; - - if let Some(pos) = delta.pos { - // update a sub-region - if let Some(user_texture) = self.textures.get_mut(&tex_id) { - let rect = glium::Rect { - left: pos[0] as _, - bottom: pos[1] as _, - width: glium_image.width, - height: glium_image.height, - }; - user_texture - .glium_texture - .main_level() - .write(rect, glium_image); - - user_texture.options = delta.options; - } - } else { - let gl_texture = - SrgbTexture2d::with_format(facade, glium_image, format, mipmaps).unwrap(); - - let user_texture = EguiTexture::new(gl_texture.into(), delta.options); - self.textures.insert(tex_id, user_texture); - } - } - - pub fn free_texture(&mut self, tex_id: egui::TextureId) { - self.textures.remove(&tex_id); - } - - fn texture(&self, texture_id: egui::TextureId) -> Option<&EguiTexture> { - self.textures.get(&texture_id) - } - - pub fn register_native_texture( - &mut self, - native: Rc, - options: TextureOptions, - ) -> egui::TextureId { - let id = egui::TextureId::User(self.next_native_tex_id); - self.next_native_tex_id += 1; - - let texture = EguiTexture::new(native, options); - self.textures.insert(id, texture); - id - } - - pub fn replace_native_texture( - &mut self, - id: egui::TextureId, - replacing: Rc, - options: TextureOptions, - ) { - let texture = EguiTexture::new(replacing, options); - self.textures.insert(id, texture); - } -} - -struct EguiTexture { - glium_texture: Rc, - options: TextureOptions, -} - -impl EguiTexture { - fn new(glium_texture: Rc, options: TextureOptions) -> Self { - Self { - glium_texture, - options, - } - } -} diff --git a/crates/egui_glium/src/shader/fragment_100es.glsl b/crates/egui_glium/src/shader/fragment_100es.glsl deleted file mode 100644 index 912f47f8f..000000000 --- a/crates/egui_glium/src/shader/fragment_100es.glsl +++ /dev/null @@ -1,38 +0,0 @@ -#version 100 - -precision mediump float; -uniform sampler2D u_sampler; -varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA -varying vec2 v_tc; - -// 0-255 sRGB from 0-1 linear -vec3 srgb_from_linear(vec3 rgb) { - bvec3 cutoff = lessThan(rgb, vec3(0.0031308)); - vec3 lower = rgb * vec3(3294.6); - vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025); - return mix(higher, lower, vec3(cutoff)); -} - -vec4 srgba_from_linear(vec4 rgba) { - return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a); -} - -// 0-1 linear from 0-255 sRGB -vec3 linear_from_srgb(vec3 srgb) { - bvec3 cutoff = lessThan(srgb, vec3(10.31475)); - vec3 lower = srgb / vec3(3294.6); - vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4)); - return mix(higher, lower, vec3(cutoff)); -} - -vec4 linear_from_srgba(vec4 srgba) { - return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0); -} - -void main() { - // WebGL doesn't come with sRGBA textures: - vec4 texture_in_gamma = texture2D(u_sampler, v_tc); - - // Multiply vertex color with texture color (in gamma space). - gl_FragColor = v_rgba_gamma * texture_in_gamma; -} diff --git a/crates/egui_glium/src/shader/fragment_120.glsl b/crates/egui_glium/src/shader/fragment_120.glsl deleted file mode 100644 index e697278d9..000000000 --- a/crates/egui_glium/src/shader/fragment_120.glsl +++ /dev/null @@ -1,31 +0,0 @@ -#version 120 - -uniform sampler2D u_sampler; -varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA -varying vec2 v_tc; - -// 0-255 sRGB from 0-1 linear -vec3 srgb_from_linear(vec3 rgb) { - bvec3 cutoff = lessThan(rgb, vec3(0.0031308)); - vec3 lower = rgb * vec3(3294.6); - vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025); - return mix(higher, lower, vec3(cutoff)); -} - -// 0-255 sRGBA from 0-1 linear -vec4 srgba_from_linear(vec4 rgba) { - return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a); -} - -// 0-1 gamma from 0-1 linear -vec4 gamma_from_linear_rgba(vec4 linear_rgba) { - return vec4(srgb_from_linear(linear_rgba.rgb) / 255.0, linear_rgba.a); -} - -void main() { - // The texture is set up with `SRGB8_ALPHA8` - vec4 texture_in_gamma = gamma_from_linear_rgba(texture2D(u_sampler, v_tc)); - - // Multiply vertex color with texture color (in gamma space). - gl_FragColor = v_rgba_gamma * texture_in_gamma; -} diff --git a/crates/egui_glium/src/shader/fragment_140.glsl b/crates/egui_glium/src/shader/fragment_140.glsl deleted file mode 100644 index 4db357f51..000000000 --- a/crates/egui_glium/src/shader/fragment_140.glsl +++ /dev/null @@ -1,32 +0,0 @@ -#version 140 - -uniform sampler2D u_sampler; -in vec4 v_rgba_gamma; -in vec2 v_tc; -out vec4 f_color; - -// 0-255 sRGB from 0-1 linear -vec3 srgb_from_linear(vec3 rgb) { - bvec3 cutoff = lessThan(rgb, vec3(0.0031308)); - vec3 lower = rgb * vec3(3294.6); - vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025); - return mix(higher, lower, vec3(cutoff)); -} - -// 0-255 sRGBA from 0-1 linear -vec4 srgba_from_linear(vec4 rgba) { - return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a); -} - -// 0-1 gamma from 0-1 linear -vec4 gamma_from_linear_rgba(vec4 linear_rgba) { - return vec4(srgb_from_linear(linear_rgba.rgb) / 255.0, linear_rgba.a); -} - -void main() { - // The texture is set up with `SRGB8_ALPHA8` - vec4 texture_in_gamma = gamma_from_linear_rgba(texture(u_sampler, v_tc)); - - // Multiply vertex color with texture color (in gamma space). - f_color = v_rgba_gamma * texture_in_gamma; -} diff --git a/crates/egui_glium/src/shader/fragment_300es.glsl b/crates/egui_glium/src/shader/fragment_300es.glsl deleted file mode 100644 index 8d4786165..000000000 --- a/crates/egui_glium/src/shader/fragment_300es.glsl +++ /dev/null @@ -1,32 +0,0 @@ -#version 300 es - -precision mediump float; -uniform sampler2D u_sampler; -varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA -varying vec2 v_tc; - -// 0-255 sRGB from 0-1 linear -vec3 srgb_from_linear(vec3 rgb) { - bvec3 cutoff = lessThan(rgb, vec3(0.0031308)); - vec3 lower = rgb * vec3(3294.6); - vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025); - return mix(higher, lower, vec3(cutoff)); -} - -// 0-255 sRGBA from 0-1 linear -vec4 srgba_from_linear(vec4 rgba) { - return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a); -} - -// 0-1 gamma from 0-1 linear -vec4 gamma_from_linear_rgba(vec4 linear_rgba) { - return vec4(srgb_from_linear(linear_rgba.rgb) / 255.0, linear_rgba.a); -} - -void main() { - // The texture is set up with `SRGB8_ALPHA8` - vec4 texture_in_gamma = gamma_from_linear_rgba(texture2D(u_sampler, v_tc)); - - // Multiply vertex color with texture color (in gamma space). - gl_FragColor = v_rgba_gamma * texture_in_gamma; -} diff --git a/crates/egui_glium/src/shader/vertex_100es.glsl b/crates/egui_glium/src/shader/vertex_100es.glsl deleted file mode 100644 index 4cc481365..000000000 --- a/crates/egui_glium/src/shader/vertex_100es.glsl +++ /dev/null @@ -1,19 +0,0 @@ -#version 100 - -precision mediump float; -uniform vec2 u_screen_size; -attribute vec2 a_pos; -attribute vec2 a_tc; -attribute vec4 a_srgba; -varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA -varying vec2 v_tc; - -void main() { - gl_Position = vec4( - 2.0 * a_pos.x / u_screen_size.x - 1.0, - 1.0 - 2.0 * a_pos.y / u_screen_size.y, - 0.0, - 1.0); - v_rgba_gamma = a_srgba / 255.0; - v_tc = a_tc; -} diff --git a/crates/egui_glium/src/shader/vertex_120.glsl b/crates/egui_glium/src/shader/vertex_120.glsl deleted file mode 100644 index 2f1a03239..000000000 --- a/crates/egui_glium/src/shader/vertex_120.glsl +++ /dev/null @@ -1,18 +0,0 @@ -#version 120 - -uniform vec2 u_screen_size; -attribute vec2 a_pos; -attribute vec4 a_srgba; // 0-255 sRGB -attribute vec2 a_tc; -varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA -varying vec2 v_tc; - -void main() { - gl_Position = vec4( - 2.0 * a_pos.x / u_screen_size.x - 1.0, - 1.0 - 2.0 * a_pos.y / u_screen_size.y, - 0.0, - 1.0); - v_rgba_gamma = a_srgba / 255.0; - v_tc = a_tc; -} diff --git a/crates/egui_glium/src/shader/vertex_140.glsl b/crates/egui_glium/src/shader/vertex_140.glsl deleted file mode 100644 index 03d0a8d76..000000000 --- a/crates/egui_glium/src/shader/vertex_140.glsl +++ /dev/null @@ -1,18 +0,0 @@ -#version 140 - -uniform vec2 u_screen_size; -in vec2 a_pos; -in vec4 a_srgba; // 0-255 sRGB -in vec2 a_tc; -out vec4 v_rgba_gamma; -out vec2 v_tc; - -void main() { - gl_Position = vec4( - 2.0 * a_pos.x / u_screen_size.x - 1.0, - 1.0 - 2.0 * a_pos.y / u_screen_size.y, - 0.0, - 1.0); - v_rgba_gamma = a_srgba / 255.0; - v_tc = a_tc; -} diff --git a/crates/egui_glium/src/shader/vertex_300es.glsl b/crates/egui_glium/src/shader/vertex_300es.glsl deleted file mode 100644 index 60fdac1c0..000000000 --- a/crates/egui_glium/src/shader/vertex_300es.glsl +++ /dev/null @@ -1,19 +0,0 @@ -#version 300 es - -precision mediump float; -uniform vec2 u_screen_size; -attribute vec2 a_pos; -attribute vec2 a_tc; -attribute vec4 a_srgba; -varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA -varying vec2 v_tc; - -void main() { - gl_Position = vec4( - 2.0 * a_pos.x / u_screen_size.x - 1.0, - 1.0 - 2.0 * a_pos.y / u_screen_size.y, - 0.0, - 1.0); - v_rgba_gamma = a_srgba / 255.0; - v_tc = a_tc; -} From 03a1471ddb61caebfa23f519eb43dcd468da8943 Mon Sep 17 00:00:00 2001 From: YgorSouza <43298013+YgorSouza@users.noreply.github.com> Date: Sat, 11 Nov 2023 18:36:40 +0100 Subject: [PATCH 16/19] Fix Table stripe pattern when combining row() and rows() (#3442) * Closes --- crates/egui_extras/src/table.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index ca09b3859..901a7a5aa 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -867,7 +867,7 @@ impl<'a> TableBody<'a> { widths: self.widths, max_used_widths: self.max_used_widths, col_index: 0, - striped: self.striped && idx % 2 == 0, + striped: self.striped && (idx + self.row_nr) % 2 == 0, height: row_height_sans_spacing, }, ); @@ -945,7 +945,7 @@ impl<'a> TableBody<'a> { widths: self.widths, max_used_widths: self.max_used_widths, col_index: 0, - striped: self.striped && row_index % 2 == 0, + striped: self.striped && (row_index + self.row_nr) % 2 == 0, height: row_height, }, ); @@ -964,7 +964,7 @@ impl<'a> TableBody<'a> { widths: self.widths, max_used_widths: self.max_used_widths, col_index: 0, - striped: self.striped && row_index % 2 == 0, + striped: self.striped && (row_index + self.row_nr) % 2 == 0, height: row_height, }, ); From b27aa27e94d8aed5fa68b83f5eb3c56ba71db3cd Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 11 Nov 2023 21:31:36 +0100 Subject: [PATCH 17/19] Add `emath::Vec2b`, replacing `egui_plot::AxisBools` (#3543) Thanks to `impl From for Vec2b` one can now shorten some builder calls, like: Previous: ```rust egui::ScrollArea::vertical() .auto_shrink([false; 2]) ``` New: ```rust egui::ScrollArea::vertical() .auto_shrink(false) ``` --- crates/egui/src/containers/scroll_area.rs | 64 ++++++++-------- crates/egui/src/containers/window.rs | 2 +- crates/egui/src/lib.rs | 2 +- .../egui_demo_app/src/apps/custom3d_glow.rs | 2 +- .../egui_demo_app/src/apps/custom3d_wgpu.rs | 2 +- crates/egui_demo_app/src/apps/http_app.rs | 2 +- crates/egui_demo_app/src/apps/image_viewer.rs | 2 +- crates/egui_demo_app/src/wrap_app.rs | 2 +- crates/egui_demo_lib/src/demo/context_menu.rs | 6 +- crates/egui_demo_lib/src/demo/plot_demo.rs | 10 +-- crates/egui_demo_lib/src/demo/scrolling.rs | 10 +-- crates/egui_demo_lib/src/demo/text_layout.rs | 2 +- .../egui_demo_lib/src/demo/window_options.rs | 6 +- crates/egui_extras/src/table.rs | 14 ++-- crates/egui_plot/src/lib.rs | 75 ++++++------------- crates/emath/src/lib.rs | 2 + crates/emath/src/vec2b.rs | 60 +++++++++++++++ examples/images/src/main.rs | 2 +- examples/keyboard_events/src/main.rs | 2 +- 19 files changed, 148 insertions(+), 119 deletions(-) create mode 100644 crates/emath/src/vec2b.rs diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 1cacef478..e50041119 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -10,13 +10,13 @@ pub struct State { pub offset: Vec2, /// Were the scroll bars visible last frame? - show_scroll: [bool; 2], + show_scroll: Vec2b, /// The content were to large to fit large frame. - content_is_too_large: [bool; 2], + content_is_too_large: Vec2b, /// Did the user interact (hover or drag) the scroll bars last frame? - scroll_bar_interaction: [bool; 2], + scroll_bar_interaction: Vec2b, /// Momentum, used for kinetic scrolling #[cfg_attr(feature = "serde", serde(skip))] @@ -28,19 +28,19 @@ pub struct State { /// Is the scroll sticky. This is true while scroll handle is in the end position /// and remains that way until the user moves the scroll_handle. Once unstuck (false) /// it remains false until the scroll touches the end position, which reenables stickiness. - scroll_stuck_to_end: [bool; 2], + scroll_stuck_to_end: Vec2b, } impl Default for State { fn default() -> Self { Self { offset: Vec2::ZERO, - show_scroll: [false; 2], - content_is_too_large: [false; 2], - scroll_bar_interaction: [false; 2], + show_scroll: Vec2b::FALSE, + content_is_too_large: Vec2b::FALSE, + scroll_bar_interaction: Vec2b::FALSE, vel: Vec2::ZERO, scroll_start_offset_from_top_left: [None; 2], - scroll_stuck_to_end: [true; 2], + scroll_stuck_to_end: Vec2b::TRUE, } } } @@ -147,9 +147,9 @@ impl ScrollBarVisibility { #[must_use = "You should call .show()"] pub struct ScrollArea { /// Do we have horizontal/vertical scrolling enabled? - scroll_enabled: [bool; 2], + scroll_enabled: Vec2b, - auto_shrink: [bool; 2], + auto_shrink: Vec2b, max_size: Vec2, min_scrolled_size: Vec2, scroll_bar_visibility: ScrollBarVisibility, @@ -164,7 +164,7 @@ pub struct ScrollArea { /// If true for vertical or horizontal the scroll wheel will stick to the /// end position until user manually changes position. It will become true /// again once scroll handle makes contact with end. - stick_to_end: [bool; 2], + stick_to_end: Vec2b, } impl ScrollArea { @@ -195,10 +195,10 @@ 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(scroll_enabled: [bool; 2]) -> Self { + pub fn new(scroll_enabled: impl Into) -> Self { Self { - scroll_enabled, - auto_shrink: [true; 2], + scroll_enabled: scroll_enabled.into(), + auto_shrink: Vec2b::TRUE, max_size: Vec2::INFINITY, min_scrolled_size: Vec2::splat(64.0), scroll_bar_visibility: Default::default(), @@ -207,7 +207,7 @@ impl ScrollArea { offset_y: None, scrolling_enabled: true, drag_to_scroll: true, - stick_to_end: [false; 2], + stick_to_end: Vec2b::FALSE, } } @@ -327,8 +327,8 @@ impl ScrollArea { /// Turn on/off scrolling on the horizontal/vertical axes. #[inline] - pub fn scroll2(mut self, scroll_enabled: [bool; 2]) -> Self { - self.scroll_enabled = scroll_enabled; + pub fn scroll2(mut self, scroll_enabled: impl Into) -> Self { + self.scroll_enabled = scroll_enabled.into(); self } @@ -365,10 +365,10 @@ impl ScrollArea { /// * If `true`, egui will add blank space outside the scroll area. /// * If `false`, egui will add blank space inside the scroll area. /// - /// Default: `[true; 2]`. + /// Default: `true`. #[inline] - pub fn auto_shrink(mut self, auto_shrink: [bool; 2]) -> Self { - self.auto_shrink = auto_shrink; + pub fn auto_shrink(mut self, auto_shrink: impl Into) -> Self { + self.auto_shrink = auto_shrink.into(); self } @@ -406,10 +406,10 @@ struct Prepared { id: Id, state: State, - auto_shrink: [bool; 2], + auto_shrink: Vec2b, /// Does this `ScrollArea` have horizontal/vertical scrolling enabled? - scroll_enabled: [bool; 2], + scroll_enabled: Vec2b, /// Smoothly interpolated boolean of whether or not to show the scroll bars. show_bars_factor: Vec2, @@ -437,7 +437,7 @@ struct Prepared { viewport: Rect, scrolling_enabled: bool, - stick_to_end: [bool; 2], + stick_to_end: Vec2b, } impl ScrollArea { @@ -470,8 +470,8 @@ impl ScrollArea { state.offset.x = offset_x.unwrap_or(state.offset.x); state.offset.y = offset_y.unwrap_or(state.offset.y); - let show_bars: [bool; 2] = match scroll_bar_visibility { - ScrollBarVisibility::AlwaysHidden => [false; 2], + let show_bars: Vec2b = match scroll_bar_visibility { + ScrollBarVisibility::AlwaysHidden => Vec2b::FALSE, ScrollBarVisibility::VisibleWhenNeeded => state.show_scroll, ScrollBarVisibility::AlwaysVisible => scroll_enabled, }; @@ -769,10 +769,10 @@ impl Prepared { let outer_rect = Rect::from_min_size(inner_rect.min, inner_rect.size() + current_bar_use); - let content_is_too_large = [ + let content_is_too_large = Vec2b::new( 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(); let is_hovering_outer_rect = ui.rect_contains_pointer(outer_rect); @@ -795,10 +795,8 @@ impl Prepared { } let show_scroll_this_frame = match scroll_bar_visibility { - ScrollBarVisibility::AlwaysHidden => [false, false], - ScrollBarVisibility::VisibleWhenNeeded => { - [content_is_too_large[0], content_is_too_large[1]] - } + ScrollBarVisibility::AlwaysHidden => Vec2b::FALSE, + ScrollBarVisibility::VisibleWhenNeeded => content_is_too_large, ScrollBarVisibility::AlwaysVisible => scroll_enabled, }; @@ -1065,12 +1063,12 @@ impl Prepared { // Only has an effect if stick_to_end is enabled but we save in // state anyway so that entering sticky mode at an arbitrary time // has appropriate effect. - state.scroll_stuck_to_end = [ + state.scroll_stuck_to_end = Vec2b::new( (state.offset[0] == available_offset[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.0), - ]; + ); state.show_scroll = show_scroll_this_frame; state.content_is_too_large = content_is_too_large; diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index af72ff98b..14e878ad9 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -272,7 +272,7 @@ impl<'open> Window<'open> { } /// Enable/disable horizontal/vertical scrolling. `false` by default. - pub fn scroll2(mut self, scroll: [bool; 2]) -> Self { + pub fn scroll2(mut self, scroll: impl Into) -> Self { self.scroll = self.scroll.scroll2(scroll); self } diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 1cd7273e6..13108416e 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -377,7 +377,7 @@ pub use epaint::emath; pub use ecolor::hex_color; pub use ecolor::{Color32, Rgba}; pub use emath::{ - lerp, pos2, remap, remap_clamp, vec2, Align, Align2, NumExt, Pos2, Rangef, Rect, Vec2, + lerp, pos2, remap, remap_clamp, vec2, Align, Align2, NumExt, Pos2, Rangef, Rect, Vec2, Vec2b, }; pub use epaint::{ mutex, diff --git a/crates/egui_demo_app/src/apps/custom3d_glow.rs b/crates/egui_demo_app/src/apps/custom3d_glow.rs index ff545bb61..3175cf4fa 100644 --- a/crates/egui_demo_app/src/apps/custom3d_glow.rs +++ b/crates/egui_demo_app/src/apps/custom3d_glow.rs @@ -24,7 +24,7 @@ impl eframe::App for Custom3d { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { egui::ScrollArea::both() - .auto_shrink([false; 2]) + .auto_shrink(false) .show(ui, |ui| { ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 0.0; diff --git a/crates/egui_demo_app/src/apps/custom3d_wgpu.rs b/crates/egui_demo_app/src/apps/custom3d_wgpu.rs index 29bbfaafe..1bac26ed3 100644 --- a/crates/egui_demo_app/src/apps/custom3d_wgpu.rs +++ b/crates/egui_demo_app/src/apps/custom3d_wgpu.rs @@ -99,7 +99,7 @@ impl eframe::App for Custom3d { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { egui::ScrollArea::both() - .auto_shrink([false; 2]) + .auto_shrink(false) .show(ui, |ui| { ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 0.0; diff --git a/crates/egui_demo_app/src/apps/http_app.rs b/crates/egui_demo_app/src/apps/http_app.rs index 90be24dd2..a118a90fc 100644 --- a/crates/egui_demo_app/src/apps/http_app.rs +++ b/crates/egui_demo_app/src/apps/http_app.rs @@ -174,7 +174,7 @@ fn ui_resource(ui: &mut egui::Ui, resource: &Resource) { ui.separator(); egui::ScrollArea::vertical() - .auto_shrink([false; 2]) + .auto_shrink(false) .show(ui, |ui| { egui::CollapsingHeader::new("Response headers") .default_open(false) diff --git a/crates/egui_demo_app/src/apps/image_viewer.rs b/crates/egui_demo_app/src/apps/image_viewer.rs index 6bc6cc05b..9754972bd 100644 --- a/crates/egui_demo_app/src/apps/image_viewer.rs +++ b/crates/egui_demo_app/src/apps/image_viewer.rs @@ -187,7 +187,7 @@ impl eframe::App for ImageViewer { }); egui::CentralPanel::default().show(ctx, |ui| { - egui::ScrollArea::new([true, true]).show(ui, |ui| { + egui::ScrollArea::both().show(ui, |ui| { let mut image = egui::Image::from_uri(&self.current_uri); image = image.uv(self.image_options.uv); image = image.bg_fill(self.image_options.bg_fill); diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index e5f458874..fe32733ad 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -68,7 +68,7 @@ impl eframe::App for ColorTestApp { ); ui.separator(); } - egui::ScrollArea::both().auto_shrink([false; 2]).show(ui, |ui| { + egui::ScrollArea::both().auto_shrink(false).show(ui, |ui| { self.color_test.ui(ui); }); }); diff --git a/crates/egui_demo_lib/src/demo/context_menu.rs b/crates/egui_demo_lib/src/demo/context_menu.rs index 96375d72e..ffcc73379 100644 --- a/crates/egui_demo_lib/src/demo/context_menu.rs +++ b/crates/egui_demo_lib/src/demo/context_menu.rs @@ -1,3 +1,5 @@ +use egui::Vec2b; + #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] enum Plot { @@ -19,7 +21,7 @@ fn sigmoid(x: f64) -> f64 { #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct ContextMenus { plot: Plot, - show_axes: [bool; 2], + show_axes: Vec2b, allow_drag: bool, allow_zoom: bool, allow_scroll: bool, @@ -33,7 +35,7 @@ impl Default for ContextMenus { fn default() -> Self { Self { plot: Plot::Sin, - show_axes: [true, true], + show_axes: Vec2b::TRUE, allow_drag: true, allow_zoom: true, allow_scroll: true, diff --git a/crates/egui_demo_lib/src/demo/plot_demo.rs b/crates/egui_demo_lib/src/demo/plot_demo.rs index cd742c1a1..6b4464dc4 100644 --- a/crates/egui_demo_lib/src/demo/plot_demo.rs +++ b/crates/egui_demo_lib/src/demo/plot_demo.rs @@ -4,9 +4,9 @@ use std::ops::RangeInclusive; use egui::*; use egui_plot::{ - Arrows, AxisBools, AxisHints, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, CoordinatesFormatter, - Corner, GridInput, GridMark, HLine, Legend, Line, LineStyle, MarkerShape, Plot, PlotImage, - PlotPoint, PlotPoints, PlotResponse, Points, Polygon, Text, VLine, + Arrows, AxisHints, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, CoordinatesFormatter, Corner, + GridInput, GridMark, HLine, Legend, Line, LineStyle, MarkerShape, Plot, PlotImage, PlotPoint, + PlotPoints, PlotResponse, Points, Polygon, Text, VLine, }; // ---------------------------------------------------------------------------- @@ -830,8 +830,8 @@ impl Default for Chart { struct ChartsDemo { chart: Chart, vertical: bool, - allow_zoom: AxisBools, - allow_drag: AxisBools, + allow_zoom: Vec2b, + allow_drag: Vec2b, } impl Default for ChartsDemo { diff --git a/crates/egui_demo_lib/src/demo/scrolling.rs b/crates/egui_demo_lib/src/demo/scrolling.rs index ddcf95550..30855f77f 100644 --- a/crates/egui_demo_lib/src/demo/scrolling.rs +++ b/crates/egui_demo_lib/src/demo/scrolling.rs @@ -146,7 +146,7 @@ impl ScrollAppearance { ui.separator(); ScrollArea::vertical() - .auto_shrink([false; 2]) + .auto_shrink(false) .scroll_bar_visibility(*visibility) .show(ui, |ui| { ui.with_layout( @@ -170,7 +170,7 @@ fn huge_content_lines(ui: &mut egui::Ui) { let text_style = TextStyle::Body; let row_height = ui.text_style_height(&text_style); let num_rows = 10_000; - ScrollArea::vertical().auto_shrink([false; 2]).show_rows( + ScrollArea::vertical().auto_shrink(false).show_rows( ui, row_height, num_rows, @@ -193,7 +193,7 @@ fn huge_content_painter(ui: &mut egui::Ui) { let num_rows = 10_000; ScrollArea::vertical() - .auto_shrink([false; 2]) + .auto_shrink(false) .show_viewport(ui, |ui, viewport| { ui.set_height(row_height * num_rows as f32); @@ -292,9 +292,7 @@ impl super::View for ScrollTo { scroll_bottom |= ui.button("Scroll to bottom").clicked(); }); - let mut scroll_area = ScrollArea::vertical() - .max_height(200.0) - .auto_shrink([false; 2]); + let mut scroll_area = ScrollArea::vertical().max_height(200.0).auto_shrink(false); if go_to_scroll_offset { scroll_area = scroll_area.vertical_scroll_offset(self.offset); } diff --git a/crates/egui_demo_lib/src/demo/text_layout.rs b/crates/egui_demo_lib/src/demo/text_layout.rs index f2512ae8c..fa02a7175 100644 --- a/crates/egui_demo_lib/src/demo/text_layout.rs +++ b/crates/egui_demo_lib/src/demo/text_layout.rs @@ -124,7 +124,7 @@ impl super::View for TextLayoutDemo { }; egui::ScrollArea::vertical() - .auto_shrink([false; 2]) + .auto_shrink(false) .show(ui, |ui| { let extra_letter_spacing = points_per_pixel * *extra_letter_spacing_pixels as f32; let line_height = (*line_height_pixels != 0) diff --git a/crates/egui_demo_lib/src/demo/window_options.rs b/crates/egui_demo_lib/src/demo/window_options.rs index a69f6cf79..228047263 100644 --- a/crates/egui_demo_lib/src/demo/window_options.rs +++ b/crates/egui_demo_lib/src/demo/window_options.rs @@ -1,3 +1,5 @@ +use egui::Vec2b; + #[derive(Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct WindowOptions { @@ -7,7 +9,7 @@ pub struct WindowOptions { collapsible: bool, resizable: bool, constrain: bool, - scroll2: [bool; 2], + scroll2: Vec2b, disabled_time: f64, anchored: bool, @@ -24,7 +26,7 @@ impl Default for WindowOptions { collapsible: true, resizable: true, constrain: true, - scroll2: [true; 2], + scroll2: Vec2b::TRUE, disabled_time: f64::NEG_INFINITY, anchored: false, anchor: egui::Align2::RIGHT_TOP, diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index 901a7a5aa..1aa23eec7 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -3,7 +3,7 @@ //! | fixed size | all available space/minimum | 30% of available width | fixed size | //! Takes all available height, so if you want something below the table, put it in a strip. -use egui::{Align, NumExt as _, Rangef, Rect, Response, ScrollArea, Ui, Vec2}; +use egui::{Align, NumExt as _, Rangef, Rect, Response, ScrollArea, Ui, Vec2, Vec2b}; use crate::{ layout::{CellDirection, CellSize}, @@ -165,7 +165,7 @@ struct TableScrollOptions { scroll_offset_y: Option, min_scrolled_height: f32, max_scroll_height: f32, - auto_shrink: [bool; 2], + auto_shrink: Vec2b, } impl Default for TableScrollOptions { @@ -178,7 +178,7 @@ impl Default for TableScrollOptions { scroll_offset_y: None, min_scrolled_height: 200.0, max_scroll_height: 800.0, - auto_shrink: [true; 2], + auto_shrink: Vec2b::TRUE, } } } @@ -335,11 +335,11 @@ impl<'a> TableBuilder<'a> { /// * If true, add blank space outside the table, keeping the table small. /// * If false, add blank space inside the table, expanding the table to fit the containing ui. /// - /// Default: `[true; 2]`. + /// Default: `true`. /// /// See [`ScrollArea::auto_shrink`] for more. - pub fn auto_shrink(mut self, auto_shrink: [bool; 2]) -> Self { - self.scroll_options.auto_shrink = auto_shrink; + pub fn auto_shrink(mut self, auto_shrink: impl Into) -> Self { + self.scroll_options.auto_shrink = auto_shrink.into(); self } @@ -577,7 +577,7 @@ impl<'a> Table<'a> { let avail_rect = ui.available_rect_before_wrap(); let mut scroll_area = ScrollArea::new([false, vscroll]) - .auto_shrink([true; 2]) + .auto_shrink(true) .drag_to_scroll(drag_to_scroll) .stick_to_bottom(stick_to_bottom) .min_scrolled_height(min_scrolled_height) diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index 7e8507a62..e276f5177 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -77,47 +77,13 @@ impl Default for CoordinatesFormatter { const MIN_LINE_SPACING_IN_POINTS: f64 = 6.0; // TODO(emilk): large enough for a wide label -/// Two bools, one for each axis (X and Y). -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct AxisBools { - pub x: bool, - pub y: bool, -} - -impl AxisBools { - #[inline] - pub fn new(x: bool, y: bool) -> Self { - Self { x, y } - } - - #[inline] - pub fn any(&self) -> bool { - self.x || self.y - } -} - -impl From for AxisBools { - #[inline] - fn from(val: bool) -> Self { - AxisBools { x: val, y: val } - } -} - -impl From<[bool; 2]> for AxisBools { - #[inline] - fn from([x, y]: [bool; 2]) -> Self { - AxisBools { x, y } - } -} - /// Information about the plot that has to persist between frames. #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[derive(Clone)] struct PlotMemory { /// Indicates if the user has modified the bounds, for example by moving or zooming, /// or if the bounds should be calculated based by included point or auto bounds. - bounds_modified: AxisBools, + bounds_modified: Vec2b, hovered_entry: Option, hidden_items: ahash::HashSet, @@ -171,7 +137,7 @@ struct CursorLinkGroups(HashMap>); #[derive(Clone)] struct LinkedBounds { bounds: PlotBounds, - bounds_modified: AxisBools, + bounds_modified: Vec2b, } #[derive(Default, Clone)] @@ -211,18 +177,18 @@ pub struct PlotResponse { pub struct Plot { id_source: Id, - center_axis: AxisBools, - allow_zoom: AxisBools, - allow_drag: AxisBools, + center_axis: Vec2b, + allow_zoom: Vec2b, + allow_drag: Vec2b, allow_scroll: bool, allow_double_click_reset: bool, allow_boxed_zoom: bool, - auto_bounds: AxisBools, + auto_bounds: Vec2b, min_auto_bounds: PlotBounds, margin_fraction: Vec2, boxed_zoom_pointer_button: PointerButton, - linked_axes: Option<(Id, AxisBools)>, - linked_cursors: Option<(Id, AxisBools)>, + linked_axes: Option<(Id, Vec2b)>, + linked_cursors: Option<(Id, Vec2b)>, min_size: Vec2, width: Option, @@ -240,8 +206,8 @@ pub struct Plot { y_axes: Vec, // default y axes legend_config: Option, show_background: bool, - show_axes: AxisBools, - show_grid: AxisBools, + show_axes: Vec2b, + show_grid: Vec2b, grid_spacers: [GridSpacer; 2], sharp_grid_lines: bool, clamp_grid: bool, @@ -357,7 +323,7 @@ impl Plot { /// Note: Allowing zoom in one axis but not the other may lead to unexpected results if used in combination with `data_aspect`. pub fn allow_zoom(mut self, on: T) -> Self where - T: Into, + T: Into, { self.allow_zoom = on.into(); self @@ -401,7 +367,7 @@ impl Plot { /// Whether to allow dragging in the plot to move the bounds. Default: `true`. pub fn allow_drag(mut self, on: T) -> Self where - T: Into, + T: Into, { self.allow_drag = on.into(); self @@ -530,6 +496,7 @@ impl Plot { } /// Whether or not to show the background [`Rect`]. + /// /// Can be useful to disable if the plot is overlaid over existing content. /// Default: `true`. pub fn show_background(mut self, show: bool) -> Self { @@ -539,16 +506,16 @@ impl Plot { /// Show axis labels and grid tick values on the side of the plot. /// - /// Default: `[true; 2]`. - pub fn show_axes(mut self, show: impl Into) -> Self { + /// Default: `true`. + pub fn show_axes(mut self, show: impl Into) -> Self { self.show_axes = show.into(); self } /// Show a grid overlay on the plot. /// - /// Default: `[true; 2]`. - pub fn show_grid(mut self, show: impl Into) -> Self { + /// Default: `true`. + pub fn show_grid(mut self, show: impl Into) -> Self { self.show_grid = show.into(); self } @@ -558,7 +525,7 @@ impl Plot { pub fn link_axis(mut self, group_id: impl Into, link_x: bool, link_y: bool) -> Self { self.linked_axes = Some(( group_id.into(), - AxisBools { + Vec2b { x: link_x, y: link_y, }, @@ -571,7 +538,7 @@ impl Plot { pub fn link_cursor(mut self, group_id: impl Into, link_x: bool, link_y: bool) -> Self { self.linked_cursors = Some(( group_id.into(), - AxisBools { + Vec2b { x: link_x, y: link_y, }, @@ -1245,7 +1212,7 @@ impl Plot { } fn axis_widgets( - show_axes: AxisBools, + show_axes: Vec2b, plot_rect: Rect, [x_axes, y_axes]: [&[AxisHints]; 2], ) -> [Vec; 2] { @@ -1616,7 +1583,7 @@ struct PreparedPlot { coordinates_formatter: Option<(Corner, CoordinatesFormatter)>, // axis_formatters: [AxisFormatter; 2], transform: PlotTransform, - show_grid: AxisBools, + show_grid: Vec2b, grid_spacers: [GridSpacer; 2], draw_cursor_x: bool, draw_cursor_y: bool, diff --git a/crates/emath/src/lib.rs b/crates/emath/src/lib.rs index 8cfe16de0..71d32de6a 100644 --- a/crates/emath/src/lib.rs +++ b/crates/emath/src/lib.rs @@ -36,6 +36,7 @@ mod rect_transform; mod rot2; pub mod smart_aim; mod vec2; +mod vec2b; pub use { align::{Align, Align2}, @@ -47,6 +48,7 @@ pub use { rect_transform::*, rot2::*, vec2::*, + vec2b::*, }; // ---------------------------------------------------------------------------- diff --git a/crates/emath/src/vec2b.rs b/crates/emath/src/vec2b.rs new file mode 100644 index 000000000..e422c9ae5 --- /dev/null +++ b/crates/emath/src/vec2b.rs @@ -0,0 +1,60 @@ +/// Two bools, one for each axis (X and Y). +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct Vec2b { + pub x: bool, + pub y: bool, +} + +impl Vec2b { + pub const FALSE: Self = Self { x: false, y: false }; + pub const TRUE: Self = Self { x: true, y: true }; + + #[inline] + pub fn new(x: bool, y: bool) -> Self { + Self { x, y } + } + + #[inline] + pub fn any(&self) -> bool { + self.x || self.y + } +} + +impl From for Vec2b { + #[inline] + fn from(val: bool) -> Self { + Vec2b { x: val, y: val } + } +} + +impl From<[bool; 2]> for Vec2b { + #[inline] + fn from([x, y]: [bool; 2]) -> Self { + Vec2b { x, y } + } +} + +impl std::ops::Index for Vec2b { + type Output = bool; + + #[inline(always)] + fn index(&self, index: usize) -> &bool { + match index { + 0 => &self.x, + 1 => &self.y, + _ => panic!("Vec2b index out of bounds: {index}"), + } + } +} + +impl std::ops::IndexMut for Vec2b { + #[inline(always)] + fn index_mut(&mut self, index: usize) -> &mut bool { + match index { + 0 => &mut self.x, + 1 => &mut self.y, + _ => panic!("Vec2b index out of bounds: {index}"), + } + } +} diff --git a/examples/images/src/main.rs b/examples/images/src/main.rs index d942a21c9..be0364e21 100644 --- a/examples/images/src/main.rs +++ b/examples/images/src/main.rs @@ -25,7 +25,7 @@ struct MyApp {} impl eframe::App for MyApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { - egui::ScrollArea::new([true, true]).show(ui, |ui| { + egui::ScrollArea::both().show(ui, |ui| { ui.image(egui::include_image!("ferris.svg")); ui.add( diff --git a/examples/keyboard_events/src/main.rs b/examples/keyboard_events/src/main.rs index a51bf1908..581ae11eb 100644 --- a/examples/keyboard_events/src/main.rs +++ b/examples/keyboard_events/src/main.rs @@ -26,7 +26,7 @@ impl eframe::App for Content { self.text.clear(); } ScrollArea::vertical() - .auto_shrink([false; 2]) + .auto_shrink(false) .stick_to_bottom(true) .show(ui, |ui| { ui.label(&self.text); From 6ba356d3d81565830393076c564acc01aeff3381 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 11 Nov 2023 21:40:02 +0100 Subject: [PATCH 18/19] Replace `Id::null()` with `Id::NULL` (#3544) Shorter and more idiomatic --- crates/egui/src/containers/popup.rs | 4 ++-- crates/egui/src/id.rs | 3 +++ crates/egui/src/widgets/color_picker.rs | 2 +- crates/egui_extras/src/syntax_highlighting.rs | 2 +- crates/egui_plot/src/lib.rs | 10 +++++----- crates/emath/src/lib.rs | 16 +++++----------- 6 files changed, 17 insertions(+), 20 deletions(-) diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index 78be080ba..a82c82ee2 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -13,11 +13,11 @@ pub(crate) struct TooltipState { impl TooltipState { pub fn load(ctx: &Context) -> Option { - ctx.data_mut(|d| d.get_temp(Id::null())) + ctx.data_mut(|d| d.get_temp(Id::NULL)) } fn store(self, ctx: &Context) { - ctx.data_mut(|d| d.insert_temp(Id::null(), self)); + ctx.data_mut(|d| d.insert_temp(Id::NULL, self)); } fn individual_tooltip_size(&self, common_id: Id, index: usize) -> Option { diff --git a/crates/egui/src/id.rs b/crates/egui/src/id.rs index 75cc9f856..9caa3bafd 100644 --- a/crates/egui/src/id.rs +++ b/crates/egui/src/id.rs @@ -35,6 +35,9 @@ impl Id { /// /// The null [`Id`] is still a valid id to use in all circumstances, /// though obviously it will lead to a lot of collisions if you do use it! + pub const NULL: Self = Self(0); + + #[deprecated = "Use Id::NULL"] pub fn null() -> Self { Self(0) } diff --git a/crates/egui/src/widgets/color_picker.rs b/crates/egui/src/widgets/color_picker.rs index 5958d0758..a3752ef68 100644 --- a/crates/egui/src/widgets/color_picker.rs +++ b/crates/egui/src/widgets/color_picker.rs @@ -442,5 +442,5 @@ fn color_cache_set(ctx: &Context, rgba: impl Into, hsva: Hsva) { // To ensure we keep hue slider when `srgba` is gray we store the full [`Hsva`] in a cache: fn use_color_cache(ctx: &Context, f: impl FnOnce(&mut FixedCache) -> R) -> R { - ctx.data_mut(|d| f(d.get_temp_mut_or_default(Id::null()))) + ctx.data_mut(|d| f(d.get_temp_mut_or_default(Id::NULL))) } diff --git a/crates/egui_extras/src/syntax_highlighting.rs b/crates/egui_extras/src/syntax_highlighting.rs index 775d7207b..8708dcf9f 100644 --- a/crates/egui_extras/src/syntax_highlighting.rs +++ b/crates/egui_extras/src/syntax_highlighting.rs @@ -248,7 +248,7 @@ impl CodeTheme { /// Show UI for changing the color theme. pub fn ui(&mut self, ui: &mut egui::Ui) { ui.horizontal_top(|ui| { - let selected_id = egui::Id::null(); + let selected_id = egui::Id::NULL; let mut selected_tt: TokenType = ui.data_mut(|d| *d.get_persisted_mut_or(selected_id, TokenType::Comment)); diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index e276f5177..882f30f2a 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -807,7 +807,7 @@ impl Plot { if let Some((name, _)) = linked_axes.as_ref() { ui.memory_mut(|memory| { let link_groups: &mut BoundsLinkGroups = - memory.data.get_temp_mut_or_default(Id::null()); + memory.data.get_temp_mut_or_default(Id::NULL); link_groups.0.remove(name); }); }; @@ -892,7 +892,7 @@ impl Plot { // Find the cursors from other plots we need to draw let draw_cursors: Vec = if let Some((id, _)) = linked_cursors.as_ref() { ui.memory_mut(|memory| { - let frames: &mut CursorLinkGroups = memory.data.get_temp_mut_or_default(Id::null()); + let frames: &mut CursorLinkGroups = memory.data.get_temp_mut_or_default(Id::NULL); let cursors = frames.0.entry(*id).or_default(); // Look for our previous frame @@ -921,7 +921,7 @@ impl Plot { if let Some((id, axes)) = linked_axes.as_ref() { ui.memory_mut(|memory| { let link_groups: &mut BoundsLinkGroups = - memory.data.get_temp_mut_or_default(Id::null()); + memory.data.get_temp_mut_or_default(Id::NULL); if let Some(linked_bounds) = link_groups.0.get(id) { if axes.x { bounds.set_x(&linked_bounds.bounds); @@ -1164,7 +1164,7 @@ impl Plot { if let Some((id, _)) = linked_cursors.as_ref() { // Push the frame we just drew to the list of frames ui.memory_mut(|memory| { - let frames: &mut CursorLinkGroups = memory.data.get_temp_mut_or_default(Id::null()); + let frames: &mut CursorLinkGroups = memory.data.get_temp_mut_or_default(Id::NULL); let cursors = frames.0.entry(*id).or_default(); cursors.push(PlotFrameCursors { id: plot_id, @@ -1177,7 +1177,7 @@ impl Plot { // Save the linked bounds. ui.memory_mut(|memory| { let link_groups: &mut BoundsLinkGroups = - memory.data.get_temp_mut_or_default(Id::null()); + memory.data.get_temp_mut_or_default(Id::NULL); link_groups.0.insert( *id, LinkedBounds { diff --git a/crates/emath/src/lib.rs b/crates/emath/src/lib.rs index 71d32de6a..3eaab9417 100644 --- a/crates/emath/src/lib.rs +++ b/crates/emath/src/lib.rs @@ -55,21 +55,15 @@ pub use { /// Helper trait to implement [`lerp`] and [`remap`]. pub trait One { - fn one() -> Self; + const ONE: Self; } impl One for f32 { - #[inline(always)] - fn one() -> Self { - 1.0 - } + const ONE: Self = 1.0; } impl One for f64 { - #[inline(always)] - fn one() -> Self { - 1.0 - } + const ONE: Self = 1.0; } /// Helper trait to implement [`lerp`] and [`remap`]. @@ -107,7 +101,7 @@ where R: Copy + Add, { let range = range.into(); - (T::one() - t) * *range.start() + t * *range.end() + (T::ONE - t) * *range.start() + t * *range.end() } /// Where in the range is this value? Returns 0-1 if within the range. @@ -174,7 +168,7 @@ where crate::emath_assert!(from.start() != from.end()); let t = (x - *from.start()) / (*from.end() - *from.start()); // Ensure no numerical inaccuracies sneak in: - if T::one() <= t { + if T::ONE <= t { *to.end() } else { lerp(to, t) From cd4669142350df7cb1535df0fc89fb0f21f37828 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Sat, 11 Nov 2023 21:58:32 +0100 Subject: [PATCH 19/19] Updated to latest wgpu (0.18.0) (#3505) Tested on M1 Mac: * native * webgl, firefox * webgpu, chrome all looking normal Updated minor ahash version because 0.8.1 got yanked. Added some deny exceptions for now - we'll have to update winit soon to resolve glow related cargo deny errors (not a big issue though since we don't expect wgpu and glow backends to be used at the same time) --- Cargo.lock | 113 +++++++++++++++++----- Cargo.toml | 4 +- crates/eframe/src/web/web_painter_wgpu.rs | 10 +- crates/egui-wgpu/src/winit.rs | 12 ++- crates/egui/Cargo.toml | 2 +- deny.toml | 3 + 6 files changed, 110 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 13ace058d..db8dd8999 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1020,7 +1020,7 @@ dependencies = [ "eframe", "egui_glow", "env_logger", - "glow", + "glow 0.12.3", ] [[package]] @@ -1227,7 +1227,7 @@ dependencies = [ "egui-wgpu", "egui-winit", "egui_glow", - "glow", + "glow 0.12.3", "glutin", "glutin-winit", "image", @@ -1366,7 +1366,7 @@ dependencies = [ "document-features", "egui", "egui-winit", - "glow", + "glow 0.12.3", "glutin", "glutin-winit", "log", @@ -1613,6 +1613,18 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin 0.9.8", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1782,8 +1794,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1844,6 +1858,18 @@ dependencies = [ "web-sys", ] +[[package]] +name = "glow" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "886c2a30b160c4c6fec8f987430c26b526b7988ca71f664e6a699ddf6f9601e4" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "glutin" version = "0.30.10" @@ -1857,7 +1883,7 @@ dependencies = [ "dispatch", "glutin_egl_sys", "glutin_glx_sys", - "glutin_wgl_sys", + "glutin_wgl_sys 0.4.0", "libloading 0.7.4", "objc2", "once_cell", @@ -1908,6 +1934,15 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "glutin_wgl_sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" +dependencies = [ + "gl_generator", +] + [[package]] name = "gobject-sys" version = "0.16.3" @@ -1940,15 +1975,16 @@ dependencies = [ [[package]] name = "gpu-allocator" -version = "0.22.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce95f9e2e11c2c6fadfce42b5af60005db06576f231f5c92550fdded43c423e8" +checksum = "40fe17c8a05d60c38c0a4e5a3c802f2f1ceb66b76c67d96ffb34bef0475a7fad" dependencies = [ "backtrace", "log", + "presser", "thiserror", "winapi", - "windows 0.44.0", + "windows 0.51.1", ] [[package]] @@ -2294,12 +2330,12 @@ dependencies = [ [[package]] name = "khronos-egl" -version = "4.1.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ "libc", - "libloading 0.7.4", + "libloading 0.8.0", "pkg-config", ] @@ -2455,9 +2491,9 @@ dependencies = [ [[package]] name = "metal" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "623b5e6cefd76e58f774bd3cc0c6f5c7615c58c03a97815245a25c3c9bdee318" +checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" dependencies = [ "bitflags 2.4.0", "block", @@ -2520,15 +2556,15 @@ dependencies = [ [[package]] name = "naga" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ceaaa4eedaece7e4ec08c55c640ba03dbb73fb812a6570a59bcf1930d0f70e" +checksum = "61d829abac9f5230a85d8cc83ec0879b4c09790208ae25b5ea031ef84562e071" dependencies = [ "bit-set", "bitflags 2.4.0", "codespan-reporting", "hexf-parse", - "indexmap 1.9.3", + "indexmap 2.0.0", "log", "num-traits", "rustc-hash", @@ -2538,6 +2574,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom", +] + [[package]] name = "ndk" version = "0.7.0" @@ -2985,6 +3030,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + [[package]] name = "prettyplease" version = "0.2.15" @@ -3246,7 +3297,7 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", + "spin 0.5.2", "untrusted", "web-sys", "winapi", @@ -3605,6 +3656,15 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "spirv" version = "0.2.0+1.5.4" @@ -4306,12 +4366,13 @@ checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" [[package]] name = "wgpu" -version = "0.17.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7472f3b69449a8ae073f6ec41d05b6f846902d92a6c45313c50cb25857b736ce" +checksum = "30e7d227c9f961f2061c26f4cb0fbd4df0ef37e056edd0931783599d6c94ef24" dependencies = [ "arrayvec", "cfg-if", + "flume", "js-sys", "log", "naga", @@ -4330,9 +4391,9 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "0.17.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecf7454d9386f602f7399225c92dd2fbdcde52c519bc8fb0bd6fbeb388075dc2" +checksum = "837e02ddcdc6d4a9b56ba4598f7fd4202a7699ab03f6ef4dcdebfad2c966aea6" dependencies = [ "arrayvec", "bit-vec", @@ -4353,9 +4414,9 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "0.17.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6654a13885a17f475e8324efb46dc6986d7aaaa98353330f8de2077b153d0101" +checksum = "1e30b9a8155c83868e82a8c5d3ce899de6c3961d2ef595de8fc168a1677fc2d8" dependencies = [ "android_system_properties", "arrayvec", @@ -4365,7 +4426,8 @@ dependencies = [ "block", "core-graphics-types", "d3d12", - "glow", + "glow 0.13.0", + "glutin_wgl_sys 0.5.0", "gpu-alloc", "gpu-allocator", "gpu-descriptor", @@ -4378,6 +4440,7 @@ dependencies = [ "metal", "naga", "objc", + "once_cell", "parking_lot", "profiling", "range-alloc", @@ -4394,9 +4457,9 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "0.17.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee64d7398d0c2f9ca48922c902ef69c42d000c759f3db41e355f4a570b052b67" +checksum = "0d5ed5f0edf0de351fe311c53304986315ce866f394a2e6df0c4b3c70774bcdd" dependencies = [ "bitflags 2.4.0", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index bda52fdaf..2391820a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,4 +37,6 @@ opt-level = 2 [workspace.dependencies] thiserror = "1.0.37" -wgpu = "0.17.0" +wgpu = "0.18.0" +# Use this to build wgpu with WebGL support on the Web *instead* of using WebGPU. +#wgpu = { version = "0.18.0", features = ["webgl"] } diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index 8865574f4..798efdba7 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -77,7 +77,7 @@ impl WebPainterWgpu { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends: options.wgpu_options.supported_backends, - dx12_shader_compiler: Default::default(), + ..Default::default() }); let canvas = super::canvas_element_or_die(canvas_id); @@ -237,7 +237,7 @@ impl WebPainter for WebPainterWgpu { b: clear_color[2] as f64, a: clear_color[3] as f64, }), - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: self.depth_texture_view.as_ref().map(|view| { @@ -245,12 +245,16 @@ impl WebPainter for WebPainterWgpu { view, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.0), - store: false, + // It is very unlikely that the depth buffer is needed after egui finished rendering + // so no need to store it. (this can improve performance on tiling GPUs like mobile chips or Apple Silicon) + store: wgpu::StoreOp::Discard, }), stencil_ops: None, } }), label: Some("egui_render"), + occlusion_query_set: None, + timestamp_writes: None, }); renderer.render(&mut render_pass, clipped_primitives, &screen_descriptor); diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index c01336f35..91eb1a435 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -107,7 +107,7 @@ impl Painter { ) -> Self { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends: configuration.supported_backends, - dx12_shader_compiler: Default::default(), + ..Default::default() }); Self { @@ -528,6 +528,7 @@ impl Painter { }); let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("egui_render"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view, resolve_target, @@ -538,7 +539,7 @@ impl Painter { b: clear_color[2] as f64, a: clear_color[3] as f64, }), - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: self.depth_texture_view.as_ref().map(|view| { @@ -546,12 +547,15 @@ impl Painter { view, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.0), - store: true, + // It is very unlikely that the depth buffer is needed after egui finished rendering + // so no need to store it. (this can improve performance on tiling GPUs like mobile chips or Apple Silicon) + store: wgpu::StoreOp::Discard, }), stencil_ops: None, } }), - label: Some("egui_render"), + timestamp_writes: None, + occlusion_query_set: None, }); renderer.render(&mut render_pass, clipped_primitives, &screen_descriptor); diff --git a/crates/egui/Cargo.toml b/crates/egui/Cargo.toml index 145f1d63c..5a6506125 100644 --- a/crates/egui/Cargo.toml +++ b/crates/egui/Cargo.toml @@ -78,7 +78,7 @@ unity = ["epaint/unity"] [dependencies] epaint = { version = "0.23.0", path = "../epaint", default-features = false } -ahash = { version = "0.8.1", default-features = false, features = [ +ahash = { version = "0.8.6", default-features = false, features = [ "no-rng", # we don't need DOS-protection, so we let users opt-in to it instead "std", ] } diff --git a/deny.toml b/deny.toml index 5bca6c829..70082fc58 100644 --- a/deny.toml +++ b/deny.toml @@ -47,6 +47,9 @@ skip = [ { name = "windows_x86_64_msvc" }, # old version via glutin { name = "windows-sys" }, # old version via glutin { name = "windows" }, # old version via accesskit + { name = "spin" }, # old version via ring through rusttls and other libraries, newer for wgpu. + { name = "glow" }, # TODO(@wumpf): Old version use for glow backend right now, newer for wgpu. Updating this trickles out to updating winit. + { name = "glutin_wgl_sys" }, # TODO(@wumpf): As above ] skip-tree = [ { name = "criterion" }, # dev-dependency