From f4b39ecc6cca7d02fb3c6fc5d0306d71c40c1790 Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Fri, 17 Feb 2023 15:06:17 +1300 Subject: [PATCH 1/9] ScrollArea override_scroll_delta() --- crates/egui/src/containers/scroll_area.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 0c2cead7023..771a4265240 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -119,6 +119,9 @@ pub struct ScrollArea { /// end position until user manually changes position. It will become true /// again once scroll handle makes contact with end. stick_to_end: [bool; 2], + + /// Override for scroll delta. Normally taken from frame_state + override_scroll_delta: Option, } impl ScrollArea { @@ -158,9 +161,11 @@ impl ScrollArea { scrolling_enabled: true, drag_to_scroll: true, stick_to_end: [false; 2], + override_scroll_delta: None, } } + /// The maximum width of the outer frame of the scroll area. /// /// Use `f32::INFINITY` if you want the scroll area to expand to fit the surrounding [`Ui`] (default). @@ -332,6 +337,11 @@ impl ScrollArea { self.stick_to_end[1] = stick; self } + + pub fn override_scroll_delta(mut self, delta: Vec2) -> Self { + self.override_scroll_delta = Some(delta); + self + } } struct Prepared { @@ -357,6 +367,7 @@ struct Prepared { scrolling_enabled: bool, stick_to_end: [bool; 2], + override_scroll_delta: Option, } impl ScrollArea { @@ -373,6 +384,7 @@ impl ScrollArea { scrolling_enabled, drag_to_scroll, stick_to_end, + override_scroll_delta, } = self; let ctx = ui.ctx().clone(); @@ -519,6 +531,7 @@ impl ScrollArea { viewport, scrolling_enabled, stick_to_end, + override_scroll_delta, } } @@ -629,6 +642,7 @@ impl Prepared { viewport: _, scrolling_enabled, stick_to_end, + override_scroll_delta, } = self; let content_size = content_ui.min_size(); @@ -702,7 +716,10 @@ impl Prepared { if scrolling_enabled && ui.rect_contains_pointer(outer_rect) { for d in 0..2 { if has_bar[d] { - let scroll_delta = ui.ctx().frame_state(|fs| fs.scroll_delta); + let scroll_delta = match override_scroll_delta { + Some(delta) => delta, + None => ui.ctx().frame_state(|fs| fs.scroll_delta) + }; let scrolling_up = state.offset[d] > 0.0 && scroll_delta[d] > 0.0; let scrolling_down = state.offset[d] < max_offset[d] && scroll_delta[d] < 0.0; From 28407b8a98e8ae119b09855ee93d7c2a77750ff8 Mon Sep 17 00:00:00 2001 From: Bu5hm4nn <“bu5hm4nn@users.noreply.github.com”> Date: Tue, 7 Mar 2023 22:41:42 -0600 Subject: [PATCH 2/9] Fix issue #2578 - `row_start_x` tracks a virtual position in the source paragraph (the one that is too long) for which length has already been processed. When creating an empty row, this position should not be updated as no glyphs were consumed from the source paragraph. - added example that would demonstrate the problem if the line was included, and that is fixed with this commit --- crates/epaint/src/text/text_layout.rs | 1 - examples/wrapping-layout/Cargo.toml | 12 +++++++++ examples/wrapping-layout/src/main.rs | 35 +++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 examples/wrapping-layout/Cargo.toml create mode 100644 examples/wrapping-layout/src/main.rs diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index e36ee1eaa3a..489b401d0f6 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -268,7 +268,6 @@ fn line_break(paragraph: &Paragraph, job: &LayoutJob, out_rows: &mut Vec, e rect: rect_from_x_range(first_row_indentation..=first_row_indentation), ends_with_newline: false, }); - row_start_x += first_row_indentation; first_row_indentation = 0.0; } else if let Some(last_kept_index) = row_break_candidates.get(job.wrap.break_anywhere) { diff --git a/examples/wrapping-layout/Cargo.toml b/examples/wrapping-layout/Cargo.toml new file mode 100644 index 00000000000..072000d8989 --- /dev/null +++ b/examples/wrapping-layout/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "wrapping-layout" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +eframe = { path = "../../crates/eframe", features = [ + "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO +] } +tracing-subscriber = "0.3" diff --git a/examples/wrapping-layout/src/main.rs b/examples/wrapping-layout/src/main.rs new file mode 100644 index 00000000000..c63cd41123f --- /dev/null +++ b/examples/wrapping-layout/src/main.rs @@ -0,0 +1,35 @@ +use eframe::{ + egui::{self, TextFormat}, + epaint::text::LayoutJob, +}; + +fn main() -> Result<(), eframe::Error> { + let native_options = eframe::NativeOptions::default(); + eframe::run_native( + "My egui App", + native_options, + Box::new(|cc| Box::new(MyEguiApp::new(cc))), + ) +} + +#[derive(Default)] +struct MyEguiApp {} + +impl MyEguiApp { + fn new(_cc: &eframe::CreationContext<'_>) -> Self { + Self::default() + } +} + +impl eframe::App for MyEguiApp { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + egui::CentralPanel::default().show(ctx, |ui| { + ui.horizontal_wrapped(|ui| { + ui.hyperlink_to("@npub1vdaeclr2mnntmyw...", "whocares"); + let text = " lnbc10u1p3lz4dppp5dsj2mh5kgqfqqxwhkrkw60stn8aph4gm2h2053xvwvvlvjm3q9eqdpqxycrqvpqd3hhgar9wfujqarfvd4k2arncqzpgxqzz6sp5vfenc5l4uafsky0w069zs329edf608ggpjjveguwxfl3xlswg5vq9qyyssqj46d5x3gsnljffm79eqwszk4mk47lkxywdp8mxum7un3qm0ztwj9jf46cm4lw2un9hk4gttgtjdrk29h27xu4e3ume20sqsna8q7xwspqqkwq7"; + let job = LayoutJob::single_section(text.to_owned(), TextFormat::default()); + ui.label(job); + }); + }); + } +} From 4c9e4df7ce9ab80bca4213e099abf3ff65b9419a Mon Sep 17 00:00:00 2001 From: Bu5hm4nn <“bu5hm4nn@users.noreply.github.com”> Date: Wed, 8 Mar 2023 15:33:20 -0600 Subject: [PATCH 3/9] Do not apply item_spacing to cursor when creating a new row --- crates/egui/src/layout.rs | 43 ++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/crates/egui/src/layout.rs b/crates/egui/src/layout.rs index 1dd77c21580..352447962cd 100644 --- a/crates/egui/src/layout.rs +++ b/crates/egui/src/layout.rs @@ -704,35 +704,37 @@ impl Layout { item_spacing: Vec2, ) { egui_assert!(!cursor.any_nan()); + let mut newline = false; if self.main_wrap { if cursor.intersects(frame_rect.shrink(1.0)) { // make row/column larger if necessary *cursor = cursor.union(frame_rect); } else { - // this is a new row or column. We temporarily use NAN for what will be filled in later. + // this is a new row or column. the cursor moves to the edge of the frame + newline = true; match self.main_dir { Direction::LeftToRight => { *cursor = Rect::from_min_max( - pos2(f32::NAN, frame_rect.min.y), + pos2(widget_rect.max.x, frame_rect.min.y), pos2(INFINITY, frame_rect.max.y), ); } Direction::RightToLeft => { *cursor = Rect::from_min_max( pos2(-INFINITY, frame_rect.min.y), - pos2(f32::NAN, frame_rect.max.y), + pos2(widget_rect.min.x, frame_rect.max.y), ); } Direction::TopDown => { *cursor = Rect::from_min_max( - pos2(frame_rect.min.x, f32::NAN), + pos2(frame_rect.min.x, widget_rect.max.y), pos2(frame_rect.max.x, INFINITY), ); } Direction::BottomUp => { *cursor = Rect::from_min_max( pos2(frame_rect.min.x, -INFINITY), - pos2(frame_rect.max.x, f32::NAN), + pos2(frame_rect.max.x, widget_rect.min.y), ); } }; @@ -748,20 +750,23 @@ impl Layout { } } - match self.main_dir { - Direction::LeftToRight => { - cursor.min.x = widget_rect.max.x + item_spacing.x; - } - Direction::RightToLeft => { - cursor.max.x = widget_rect.min.x - item_spacing.x; - } - Direction::TopDown => { - cursor.min.y = widget_rect.max.y + item_spacing.y; - } - Direction::BottomUp => { - cursor.max.y = widget_rect.min.y - item_spacing.y; - } - }; + // apply item_spacing unless this is a newline + if !newline { + match self.main_dir { + Direction::LeftToRight => { + cursor.min.x = widget_rect.max.x + item_spacing.x; + } + Direction::RightToLeft => { + cursor.max.x = widget_rect.min.x - item_spacing.x; + } + Direction::TopDown => { + cursor.min.y = widget_rect.max.y + item_spacing.y; + } + Direction::BottomUp => { + cursor.max.y = widget_rect.min.y - item_spacing.y; + } + }; + } } /// Move to the next row in a wrapping layout. From b4e82ca31f4ab8246141fbafd3426b66d9de9c7d Mon Sep 17 00:00:00 2001 From: Bu5hm4nn <“bu5hm4nn@users.noreply.github.com”> Date: Thu, 9 Mar 2023 17:11:24 -0600 Subject: [PATCH 4/9] Fix issue #2578 There was confusion in the code over how to break when on a non-empty visual row (`first_row_indentation > 0.0`), causing text to be shifted left outside the ui frame. This is the case for example when another label has already been placed in this `ui.horizontal_wrapped()`. This fix will not create an empty row, essentially starting a newline, but rather try to fit as much text as possible on the existing row. IMO this is the desired use of a wrapping layout. I've also added an example that would demonstrate the problem if the line was included, and that is fixed with this commit --- Cargo.lock | 8 +++ crates/epaint/src/text/text_layout.rs | 74 +++++++++++++++++++-------- examples/wrapping-layout/src/main.rs | 39 ++++++++++---- 3 files changed, 89 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 554e41ec10f..47503ef23e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4661,6 +4661,14 @@ dependencies = [ "memchr", ] +[[package]] +name = "wrapping-layout" +version = "0.1.0" +dependencies = [ + "eframe", + "tracing-subscriber", +] + [[package]] name = "x11-dl" version = "2.21.0" diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index 489b401d0f6..0e16ad9f6ab 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -147,6 +147,9 @@ fn layout_section( paragraph.empty_paragraph_height = line_height; // TODO(emilk): replace this hack with actually including `\n` in the glyphs? } + // TODO(bu5hm4nn): in a label widget, `leading_space` is used to adjust for existing text in a screen row, + // but the comment on `LayoutSection::leading_space` makes it clear it was originally intended for typographical + // indentation and not for screen layout paragraph.cursor_x += leading_space; let mut last_glyph_id = None; @@ -244,33 +247,20 @@ fn line_break(paragraph: &Paragraph, job: &LayoutJob, out_rows: &mut Vec, e let mut first_row_indentation = paragraph.glyphs[0].pos.x; let mut row_start_x = 0.0; let mut row_start_idx = 0; + let mut non_empty_rows = 0; for i in 0..paragraph.glyphs.len() { - if job.wrap.max_rows <= out_rows.len() { - *elided = true; + let potential_row_width = paragraph.glyphs[i].max_x() - row_start_x - first_row_indentation; + + if job.wrap.max_rows > 0 && non_empty_rows >= job.wrap.max_rows { break; } - let potential_row_width = paragraph.glyphs[i].max_x() - row_start_x; - - if job.wrap.max_width < potential_row_width { - // Row break: - - if first_row_indentation > 0.0 - && !row_break_candidates.has_good_candidate(job.wrap.break_anywhere) - { - // Allow the first row to be completely empty, because we know there will be more space on the next row: - // TODO(emilk): this records the height of this first row as zero, though that is probably fine since first_row_indentation usually comes with a first_row_min_height. - out_rows.push(Row { - section_index_at_start: paragraph.section_index_at_start, - glyphs: vec![], - visuals: Default::default(), - rect: rect_from_x_range(first_row_indentation..=first_row_indentation), - ends_with_newline: false, - }); - first_row_indentation = 0.0; - } else if let Some(last_kept_index) = row_break_candidates.get(job.wrap.break_anywhere) - { + // (bu5hm4nn): we want to actually allow as much text as possible on the first line so + // we don't need a special case for the first row, but we need to subtract + // the first_row_indentation from the allowed max width + if potential_row_width > (job.wrap.max_width - first_row_indentation) { + if let Some(last_kept_index) = row_break_candidates.get(job.wrap.break_anywhere) { let glyphs: Vec = paragraph.glyphs[row_start_idx..=last_kept_index] .iter() .copied() @@ -296,6 +286,12 @@ fn line_break(paragraph: &Paragraph, job: &LayoutJob, out_rows: &mut Vec, e row_start_idx = last_kept_index + 1; row_start_x = paragraph.glyphs[row_start_idx].pos.x; row_break_candidates = Default::default(); + non_empty_rows += 1; + + // (bu5hm4nn) first row indentation gets consumed the first time it's used + if first_row_indentation > 0.0 { + first_row_indentation = 0.0 + } } else { // Found no place to break, so we have to overrun wrap_width. } @@ -924,6 +920,7 @@ impl RowBreakCandidates { .flatten() } + #[allow(dead_code)] fn has_good_candidate(&self, break_anywhere: bool) -> bool { if break_anywhere { self.any.is_some() @@ -1061,3 +1058,36 @@ mod tests { ); } } + +#[test] +fn test_line_break_first_row_not_empty() { + let mut fonts = FontsImpl::new(1.0, 1024, super::FontDefinitions::default()); + let mut layout_job = LayoutJob::single_section( + "SomeSuperLongTextThatDoesNotHaveAnyGoodBreakCandidatesButStillNeedsToBeBroken".into(), + super::TextFormat::default(), + ); + + // a small area + layout_job.wrap.max_width = 110.0; + + // give the first row a leading space, simulating that there already is + // text in this visual row + layout_job.sections.first_mut().unwrap().leading_space = 50.0; + + let galley = super::layout(&mut fonts, layout_job.into()); + assert_eq!( + galley + .rows + .iter() + .map(|row| row.glyphs.iter().map(|g| g.chr).collect::()) + .collect::>(), + vec![ + "SomeSup", + "erLongTextThat", + "DoesNotHaveAn", + "yGoodBreakCand", + "idatesButStillNe", + "edsToBeBroken" + ] + ); +} diff --git a/examples/wrapping-layout/src/main.rs b/examples/wrapping-layout/src/main.rs index c63cd41123f..ddeb45d48da 100644 --- a/examples/wrapping-layout/src/main.rs +++ b/examples/wrapping-layout/src/main.rs @@ -1,13 +1,17 @@ use eframe::{ - egui::{self, TextFormat}, - epaint::text::LayoutJob, + egui::{self, WidgetText}, + emath::Align, + epaint::Stroke, }; fn main() -> Result<(), eframe::Error> { - let native_options = eframe::NativeOptions::default(); + let options = eframe::NativeOptions { + initial_window_size: Some(egui::vec2(380.0, 440.0)), + ..Default::default() + }; eframe::run_native( - "My egui App", - native_options, + "Horizontal Wrapped Layouts", + options, Box::new(|cc| Box::new(MyEguiApp::new(cc))), ) } @@ -24,12 +28,27 @@ impl MyEguiApp { impl eframe::App for MyEguiApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { - ui.horizontal_wrapped(|ui| { + ui.horizontal_wrapped(|ui| { ui.hyperlink_to("@npub1vdaeclr2mnntmyw...", "whocares"); - let text = " lnbc10u1p3lz4dppp5dsj2mh5kgqfqqxwhkrkw60stn8aph4gm2h2053xvwvvlvjm3q9eqdpqxycrqvpqd3hhgar9wfujqarfvd4k2arncqzpgxqzz6sp5vfenc5l4uafsky0w069zs329edf608ggpjjveguwxfl3xlswg5vq9qyyssqj46d5x3gsnljffm79eqwszk4mk47lkxywdp8mxum7un3qm0ztwj9jf46cm4lw2un9hk4gttgtjdrk29h27xu4e3ume20sqsna8q7xwspqqkwq7"; - let job = LayoutJob::single_section(text.to_owned(), TextFormat::default()); - ui.label(job); - }); + let text = " LotsOfTextPrecededByASpace5kgqfqqxwhkrkw60stn8aph4gm2h2053xvwvvlvjm3q9eqdpqxycrqvpqd3hhgar9wfujqarfvd4k2arncqzpgxqzz6sp5vfenc5l4uafsky0w069zs329edf608ggpjjveguwxfl3xlswg5vq9qyyssqj46d5x3gsnljffm79eqwszk4mk47lkxywdp8mxum7un3qm0ztwj9jf46cm4lw2un9hk4gttgtjdrk29h27xu4e3ume20sqsna8q7xwspqqkwq7"; + ui.label(text); + ui.style_mut().visuals.widgets.noninteractive.fg_stroke = Stroke::new( 1.0, eframe::epaint::Color32::RED ); + ui.label("More text followed by two newlines\n\n"); + ui.style_mut().visuals.widgets.noninteractive.fg_stroke = Stroke::new( 1.0, eframe::epaint::Color32::GREEN ); + ui.label("more text, no newline"); + ui.reset_style(); + }); + ui.separator(); + ui.horizontal_wrapped(|ui| { + ui.label("Hyperlink no newline:"); + let url = "https://i.nostrimg.com/c72f5e1a2e162fad2625e15651a654465c06016016f7743b496021cafa2a524e/file.jpeg"; + ui.hyperlink_to( url, url ); + ui.end_row(); + ui.label("Hyperlink break_anywhere=true"); + let mut job = WidgetText::from(url).into_text_job(ui.style(), egui::FontSelection::Default, Align::LEFT); + job.job.wrap.break_anywhere = true; + ui.hyperlink_to( job.job, url ); + }); }); } } From 736a824aff58d65f3da40a5b67a082a5de3dd4c4 Mon Sep 17 00:00:00 2001 From: Nethanja Focking Date: Thu, 13 Apr 2023 16:20:56 -0600 Subject: [PATCH 5/9] Fix https://github.com/mikedilger/gossip/issues/336 and https://github.com/mikedilger/gossip/issues/347 by ensuring ui.placer.region.cursor is set to the correct line height. --- crates/egui/src/widgets/label.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/egui/src/widgets/label.rs b/crates/egui/src/widgets/label.rs index 288fcb422e8..0a222f591c5 100644 --- a/crates/egui/src/widgets/label.rs +++ b/crates/egui/src/widgets/label.rs @@ -152,6 +152,16 @@ impl Label { !text_galley.galley.rows.is_empty(), "Galleys are never empty" ); + + // set the row height to ensure the cursor advancement is correct. when creating a child ui such as with + // ui.horizontal_wrapped, the initial cursor will be set to the height of the child ui. this can lead + // to the cursor not advancing to the second row but rather expanding the height of the cursor. + // + // note that we do not set the row height earlier in this function as we do want to allow populating + // `first_row_min_height` above. however it is crucial the placer knows the actual row height by + // setting the cursor height before ui.allocate_rect() gets called. + ui.set_row_height(text_galley.galley.rows[0].height()); + // collect a response from many rows: let rect = text_galley.galley.rows[0] .rect From 439487ca4d1d49b37d37c9d03bd1755c7be34de3 Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Sat, 29 Apr 2023 07:36:57 +1200 Subject: [PATCH 6/9] cargo fmt --- crates/egui/src/containers/scroll_area.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 771a4265240..7a0fb360f26 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -165,7 +165,6 @@ impl ScrollArea { } } - /// The maximum width of the outer frame of the scroll area. /// /// Use `f32::INFINITY` if you want the scroll area to expand to fit the surrounding [`Ui`] (default). @@ -718,7 +717,7 @@ impl Prepared { if has_bar[d] { let scroll_delta = match override_scroll_delta { Some(delta) => delta, - None => ui.ctx().frame_state(|fs| fs.scroll_delta) + None => ui.ctx().frame_state(|fs| fs.scroll_delta), }; let scrolling_up = state.offset[d] > 0.0 && scroll_delta[d] > 0.0; From 0ca9bf484b00252d0e4b5e79a031cb1aa3256f7b Mon Sep 17 00:00:00 2001 From: Bu5hm4nn <“bu5hm4nn@users.noreply.github.com”> Date: Sat, 6 May 2023 10:32:56 -0600 Subject: [PATCH 7/9] Export CollapsibleState (it has all the documentation to be user-facing) --- crates/egui/src/containers/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/containers/mod.rs b/crates/egui/src/containers/mod.rs index 53e8e7e2ce0..4bb5bb4e90c 100644 --- a/crates/egui/src/containers/mod.rs +++ b/crates/egui/src/containers/mod.rs @@ -14,7 +14,7 @@ pub(crate) mod window; pub use { area::Area, - collapsing_header::{CollapsingHeader, CollapsingResponse}, + collapsing_header::{CollapsingHeader, CollapsingResponse, CollapsingState}, combo_box::*, frame::Frame, panel::{CentralPanel, SidePanel, TopBottomPanel}, From 50393e4f34ac6246b8c2424e42fbe5b95e4b4452 Mon Sep 17 00:00:00 2001 From: Bu5hm4nn <“bu5hm4nn@users.noreply.github.com”> Date: Wed, 17 May 2023 23:04:03 -0600 Subject: [PATCH 8/9] Add public static function `ScrollArea::is_scrolling()` --- crates/egui/src/containers/scroll_area.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 7a0fb360f26..cba0545db46 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -124,6 +124,20 @@ pub struct ScrollArea { override_scroll_delta: Option, } +impl ScrollArea { + pub fn is_scrolling(ui: &Ui, id_source: Id) -> bool { + let id = ui.make_persistent_id(id_source); + let scroll_target = ui.ctx().frame_state(|state| { + state.scroll_target[0].is_some() || state.scroll_target[1].is_some() + }); + if let Some(state) = State::load(ui.ctx(), id) { + state.vel != Vec2::ZERO || scroll_target + } else { + false + } + } +} + impl ScrollArea { /// Create a horizontal scroll area. pub fn horizontal() -> Self { From 17b418dfe1f9a6966e2b25be102fe992a151b830 Mon Sep 17 00:00:00 2001 From: Bu5hm4nn Date: Mon, 9 Oct 2023 13:39:54 -0600 Subject: [PATCH 9/9] Allow optional overriding of panel (in panel layout) widget styles --- crates/egui/src/containers/panel.rs | 38 +++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index f5267e0f0c7..52d134eba29 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -98,6 +98,7 @@ pub struct SidePanel { show_separator_line: bool, default_width: f32, width_range: Rangef, + visuals: Option, } impl SidePanel { @@ -121,6 +122,7 @@ impl SidePanel { show_separator_line: true, default_width: 200.0, width_range: Rangef::new(96.0, f32::INFINITY), + visuals: None, } } @@ -190,6 +192,12 @@ impl SidePanel { self.frame = Some(frame); self } + + /// Optionally override visual style + pub fn visuals(mut self, visuals: style::Widgets) -> Self { + self.visuals = Some(visuals); + self + } } impl SidePanel { @@ -216,6 +224,7 @@ impl SidePanel { show_separator_line, default_width, width_range, + visuals, } = self; let available_rect = ui.available_rect_before_wrap(); @@ -296,15 +305,28 @@ impl SidePanel { PanelState { rect }.store(ui.ctx(), id); { - let stroke = if is_resizing { - ui.style().visuals.widgets.active.fg_stroke // highly visible - } else if resize_hover { - ui.style().visuals.widgets.hovered.fg_stroke // highly visible - } else if show_separator_line { - // TODO(emilk): distinguish resizable from non-resizable - ui.style().visuals.widgets.noninteractive.bg_stroke // dim + let stroke = if let Some(widgets) = visuals { + if is_resizing { + widgets.active.fg_stroke // highly visible + } else if resize_hover { + widgets.hovered.fg_stroke // highly visible + } else if show_separator_line { + // TODO(emilk): distinguish resizable from non-resizable + widgets.noninteractive.bg_stroke // dim + } else { + Stroke::NONE + } } else { - Stroke::NONE + if is_resizing { + ui.style().visuals.widgets.active.fg_stroke // highly visible + } else if resize_hover { + ui.style().visuals.widgets.hovered.fg_stroke // highly visible + } else if show_separator_line { + // TODO(emilk): distinguish resizable from non-resizable + ui.style().visuals.widgets.noninteractive.bg_stroke // dim + } else { + Stroke::NONE + } }; // TODO(emilk): draw line on top of all panels in this ui when https://github.com/emilk/egui/issues/1516 is done // In the meantime: nudge the line so its inside the panel, so it won't be covered by neighboring panel