From a1244f313db69a3bdf5a66a4c4d07e0a681a2f1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20N=C3=B6lle?= Date: Tue, 21 Jan 2025 20:55:38 +0100 Subject: [PATCH] Remove state from segmented control widget --- crates/hulk_widgets/src/segmented_control.rs | 73 ++++++-------------- tools/twix/src/panels/behavior_simulator.rs | 11 +-- tools/vista/src/app.rs | 42 ++++------- tools/widget_gallery/src/main.rs | 20 +++--- 4 files changed, 54 insertions(+), 92 deletions(-) diff --git a/crates/hulk_widgets/src/segmented_control.rs b/crates/hulk_widgets/src/segmented_control.rs index 45422e3d1c..dd934c64f3 100644 --- a/crates/hulk_widgets/src/segmented_control.rs +++ b/crates/hulk_widgets/src/segmented_control.rs @@ -1,26 +1,20 @@ -use egui::{ - vec2, Align2, Context, Id, InnerResponse, Key, Rect, Response, Rounding, Sense, TextStyle, Ui, - Widget, -}; +use egui::{vec2, Align2, Id, Key, Rect, Response, Rounding, Sense, TextStyle, Ui, Widget}; const ANIMATION_TIME_SECONDS: f32 = 0.1; pub struct SegmentedControl<'ui, T> { - selectables: &'ui [T], id: Id, + selected: &'ui mut usize, + selectables: &'ui [T], rounding: Option, text_style: TextStyle, } -#[derive(Debug, Default, Clone)] -struct SegmentedControlState { - selected: usize, -} - impl<'ui, T: ToString> SegmentedControl<'ui, T> { - pub fn new(id: impl Into, selectables: &'ui [T]) -> Self { + pub fn new(id: impl Into, selected: &'ui mut usize, selectables: &'ui [T]) -> Self { SegmentedControl { id: id.into(), + selected, selectables, rounding: None, text_style: TextStyle::Body, @@ -31,25 +25,20 @@ impl<'ui, T: ToString> SegmentedControl<'ui, T> { self.rounding = Some(rounding.into()); self } +} - pub fn ui(self, ui: &mut Ui) -> InnerResponse<&'ui T> { - let mut state = load_state(ui.ctx(), self.id); - let response = self.show(ui, &mut state); - let selected = &self.selectables[state.selected]; - save_state(ui.ctx(), self.id, state); - InnerResponse::new(selected, response) - } - - fn show(&self, ui: &mut Ui, state: &mut SegmentedControlState) -> Response { +impl Widget for SegmentedControl<'_, T> { + fn ui(mut self, ui: &mut Ui) -> Response { + let this = &mut self; let width = ui.available_width(); let text_style = ui .style() .text_styles - .get(&self.text_style) + .get(&this.text_style) .expect("failed to get text style") .clone(); let text_size = text_style.size * ui.ctx().pixels_per_point(); - let rounding = self + let rounding = this .rounding .unwrap_or(ui.style().noninteractive().rounding); @@ -58,41 +47,41 @@ impl<'ui, T: ToString> SegmentedControl<'ui, T> { if response.contains_pointer() { ui.input(|reader| { if reader.key_pressed(Key::ArrowLeft) || reader.key_pressed(Key::ArrowDown) { - state.selected = state.selected.saturating_sub(1); + *this.selected = this.selected.saturating_sub(1); response.mark_changed(); } else if reader.key_pressed(Key::ArrowRight) || reader.key_pressed(Key::ArrowUp) { - state.selected = (state.selected + 1).min(self.selectables.len() - 1); + *this.selected = (*this.selected + 1).min(this.selectables.len() - 1); response.mark_changed(); } }) } painter.rect_filled(response.rect, rounding, ui.style().visuals.extreme_bg_color); - let text_rects = text_rects(response.rect, self.selectables.len()); + let text_rects = text_rects(response.rect, this.selectables.len()); let offset = text_rects[0].width(); let translation = ui.ctx().animate_value_with_time( - self.id, - offset * state.selected as f32, + this.id, + offset * *this.selected as f32, ANIMATION_TIME_SECONDS, ); let selector_rect = text_rects[0].translate(vec2(translation, 0.0)).shrink(2.0); let selector_response = - ui.interact(selector_rect, self.id.with("selector"), Sense::click()); + ui.interact(selector_rect, this.id.with("selector"), Sense::click()); let selector_style = ui.style().interact(&selector_response); painter.rect_filled(selector_rect, rounding, selector_style.bg_fill); let noninteractive_style = ui.style().noninteractive(); - for (idx, (&rect, text)) in text_rects.iter().zip(self.selectables.iter()).enumerate() { - let label_response = ui.interact(rect, self.id.with(idx), Sense::click()); + for (idx, (&rect, text)) in text_rects.iter().zip(this.selectables.iter()).enumerate() { + let label_response = ui.interact(rect, this.id.with(idx), Sense::click()); let style = ui.style().interact(&response); - let show_line = idx > 0 && state.selected != idx && state.selected + 1 != idx; + let show_line = idx > 0 && *this.selected != idx && *this.selected + 1 != idx; { let animated_height = ui .ctx() - .animate_bool(self.id.with("vline").with(idx), show_line); + .animate_bool(this.id.with("vline").with(idx), show_line); let height = vec2(0.0, rect.height() - 4.0); let center = rect.left_center(); @@ -107,7 +96,7 @@ impl<'ui, T: ToString> SegmentedControl<'ui, T> { } if label_response.clicked() { - state.selected = idx; + *this.selected = idx; response.mark_changed(); } painter.text( @@ -122,24 +111,6 @@ impl<'ui, T: ToString> SegmentedControl<'ui, T> { } } -impl Widget for SegmentedControl<'_, T> { - fn ui(self, ui: &mut Ui) -> Response { - let mut state = load_state(ui.ctx(), self.id); - let response = self.show(ui, &mut state); - save_state(ui.ctx(), self.id, state); - response - } -} - -fn load_state(ctx: &Context, id: Id) -> SegmentedControlState { - let persisted = ctx.data_mut(|reader| reader.get_temp(id)); - persisted.unwrap_or_default() -} - -fn save_state(ctx: &Context, id: Id, state: SegmentedControlState) { - ctx.data_mut(|writer| writer.insert_temp(id, state)); -} - fn text_rects(mut rect: Rect, number_of_texts: usize) -> Vec { let base_width = rect.width() / number_of_texts as f32; let base_rect = { diff --git a/tools/twix/src/panels/behavior_simulator.rs b/tools/twix/src/panels/behavior_simulator.rs index 29c3051d7e..15b2c1e5c7 100644 --- a/tools/twix/src/panels/behavior_simulator.rs +++ b/tools/twix/src/panels/behavior_simulator.rs @@ -109,10 +109,13 @@ impl Widget for &mut BehaviorSimulatorPanel { ui.add_space(50.0); let robots = (1..=7).collect::>(); - let robot_selection = - SegmentedControl::new("robot-selector", &robots).ui(ui); - self.selected_robot = *robot_selection.inner; - if robot_selection.response.changed() { + let response = SegmentedControl::new( + "robot-selector", + &mut self.selected_robot, + &robots, + ) + .ui(ui); + if response.changed() { self.nao.write( "parameters.selected_robot", TextOrBinary::Text(self.selected_robot.into()), diff --git a/tools/vista/src/app.rs b/tools/vista/src/app.rs index 8c0323929b..9e1bbef835 100644 --- a/tools/vista/src/app.rs +++ b/tools/vista/src/app.rs @@ -1,41 +1,27 @@ -use std::fmt::{Display, Error, Formatter}; - use eframe::{ egui::{ pos2, vec2, Align2, CentralPanel, Color32, FontId, Key, Modifiers, ScrollArea, Shape, - TopBottomPanel, + TopBottomPanel, Widget, }, epaint::{PathStroke, QuadraticBezierShape}, App, CreationContext, }; use hulk_manifest::collect_hulk_cyclers; +use hulk_widgets::SegmentedControl; use repository::Repository; use source_analyzer::{contexts::Field, cyclers::Cyclers}; pub struct DependencyInspector { - _repository: Repository, cyclers: Cyclers, selected_cycler: usize, selected_node_index: Option, } -struct NamedIndex<'a> { - index: usize, - name: &'a str, -} - -impl Display for NamedIndex<'_> { - fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), Error> { - formatter.write_str(self.name) - } -} - impl DependencyInspector { pub fn new(_creation_context: &CreationContext, repository: Repository) -> Self { let cyclers = collect_hulk_cyclers(repository.crates_directory()).unwrap(); Self { - _repository: repository, cyclers, selected_cycler: 0, selected_node_index: None, @@ -50,16 +36,12 @@ impl App for DependencyInspector { .cyclers .cyclers .iter() - .enumerate() - .map(|(index, cycler)| NamedIndex { - index, - name: &cycler.name, - }) + .map(|cycler| &cycler.name) .collect(); let response = - hulk_widgets::SegmentedControl::new("cycler selector", &cycler_names).ui(ui); - if response.response.changed() { - self.selected_cycler = response.inner.index; + SegmentedControl::new("cycler selector", &mut self.selected_cycler, &cycler_names) + .ui(ui); + if response.changed() { self.selected_node_index = None; } }); @@ -86,13 +68,17 @@ impl App for DependencyInspector { let mut node_selection_changed = false; ui.input_mut(|input| { if input.consume_key(Modifiers::NONE, Key::ArrowUp) { - self.selected_node_index = - Some(self.selected_node_index.unwrap_or(0).saturating_sub(1)); + self.selected_node_index = Some(match self.selected_node_index { + Some(old) => old.saturating_sub(1), + None => nodes.len() - 1, + }); node_selection_changed = true; } if input.consume_key(Modifiers::NONE, Key::ArrowDown) { - self.selected_node_index = - Some((self.selected_node_index.unwrap_or(0) + 1).min(nodes.len() - 1)); + self.selected_node_index = Some(match self.selected_node_index { + Some(old) => (old + 1).min(nodes.len() - 1), + None => 0, + }); node_selection_changed = true; } if input.consume_key(Modifiers::NONE, Key::Escape) { diff --git a/tools/widget_gallery/src/main.rs b/tools/widget_gallery/src/main.rs index 43f918c113..740d59e552 100644 --- a/tools/widget_gallery/src/main.rs +++ b/tools/widget_gallery/src/main.rs @@ -1,5 +1,5 @@ use eframe::{ - egui::{CentralPanel, Context}, + egui::{CentralPanel, Context, Widget}, run_native, App, Frame, }; use hulk_widgets::{CompletionEdit, SegmentedControl}; @@ -15,7 +15,8 @@ fn main() -> eframe::Result { #[derive(Debug, Clone)] struct AppState { searchables: Vec, - selected: String, + selected: usize, + search_text: String, } impl AppState { @@ -23,7 +24,8 @@ impl AppState { let searchables: Vec<_> = (1..100).map(|x| x.to_string()).collect(); Self { searchables, - selected: String::new(), + selected: 0, + search_text: String::new(), } } } @@ -35,10 +37,10 @@ impl App for AppState { let response = ui.add(CompletionEdit::new( "completion-edit", &self.searchables, - &mut self.selected, + &mut self.search_text, )); if response.changed() { - println!("Selected: {}", self.selected); + println!("Selected: {}", self.search_text); } if ui.button("Focus").clicked() { @@ -51,10 +53,10 @@ impl App for AppState { ui.horizontal(|ui| { ui.columns(2, |columns| { let selectables = ["Dies", "Das", "Ananas", "Foo", "Bar", "Baz"]; - let selected = SegmentedControl::new("segmented-control", &selectables) - .ui(&mut columns[0]) - .inner; - columns[1].label(*selected); + SegmentedControl::new("segmented-control", &mut self.selected, &selectables) + .ui(&mut columns[0]); + + columns[1].label(selectables[self.selected]); }) }) });