Skip to content

Commit a1244f3

Browse files
committed
Remove state from segmented control widget
1 parent 81b53ed commit a1244f3

File tree

4 files changed

+54
-92
lines changed

4 files changed

+54
-92
lines changed

crates/hulk_widgets/src/segmented_control.rs

+22-51
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,20 @@
1-
use egui::{
2-
vec2, Align2, Context, Id, InnerResponse, Key, Rect, Response, Rounding, Sense, TextStyle, Ui,
3-
Widget,
4-
};
1+
use egui::{vec2, Align2, Id, Key, Rect, Response, Rounding, Sense, TextStyle, Ui, Widget};
52

63
const ANIMATION_TIME_SECONDS: f32 = 0.1;
74

85
pub struct SegmentedControl<'ui, T> {
9-
selectables: &'ui [T],
106
id: Id,
7+
selected: &'ui mut usize,
8+
selectables: &'ui [T],
119
rounding: Option<Rounding>,
1210
text_style: TextStyle,
1311
}
1412

15-
#[derive(Debug, Default, Clone)]
16-
struct SegmentedControlState {
17-
selected: usize,
18-
}
19-
2013
impl<'ui, T: ToString> SegmentedControl<'ui, T> {
21-
pub fn new(id: impl Into<Id>, selectables: &'ui [T]) -> Self {
14+
pub fn new(id: impl Into<Id>, selected: &'ui mut usize, selectables: &'ui [T]) -> Self {
2215
SegmentedControl {
2316
id: id.into(),
17+
selected,
2418
selectables,
2519
rounding: None,
2620
text_style: TextStyle::Body,
@@ -31,25 +25,20 @@ impl<'ui, T: ToString> SegmentedControl<'ui, T> {
3125
self.rounding = Some(rounding.into());
3226
self
3327
}
28+
}
3429

35-
pub fn ui(self, ui: &mut Ui) -> InnerResponse<&'ui T> {
36-
let mut state = load_state(ui.ctx(), self.id);
37-
let response = self.show(ui, &mut state);
38-
let selected = &self.selectables[state.selected];
39-
save_state(ui.ctx(), self.id, state);
40-
InnerResponse::new(selected, response)
41-
}
42-
43-
fn show(&self, ui: &mut Ui, state: &mut SegmentedControlState) -> Response {
30+
impl<T: ToString> Widget for SegmentedControl<'_, T> {
31+
fn ui(mut self, ui: &mut Ui) -> Response {
32+
let this = &mut self;
4433
let width = ui.available_width();
4534
let text_style = ui
4635
.style()
4736
.text_styles
48-
.get(&self.text_style)
37+
.get(&this.text_style)
4938
.expect("failed to get text style")
5039
.clone();
5140
let text_size = text_style.size * ui.ctx().pixels_per_point();
52-
let rounding = self
41+
let rounding = this
5342
.rounding
5443
.unwrap_or(ui.style().noninteractive().rounding);
5544

@@ -58,41 +47,41 @@ impl<'ui, T: ToString> SegmentedControl<'ui, T> {
5847
if response.contains_pointer() {
5948
ui.input(|reader| {
6049
if reader.key_pressed(Key::ArrowLeft) || reader.key_pressed(Key::ArrowDown) {
61-
state.selected = state.selected.saturating_sub(1);
50+
*this.selected = this.selected.saturating_sub(1);
6251
response.mark_changed();
6352
} else if reader.key_pressed(Key::ArrowRight) || reader.key_pressed(Key::ArrowUp) {
64-
state.selected = (state.selected + 1).min(self.selectables.len() - 1);
53+
*this.selected = (*this.selected + 1).min(this.selectables.len() - 1);
6554
response.mark_changed();
6655
}
6756
})
6857
}
6958
painter.rect_filled(response.rect, rounding, ui.style().visuals.extreme_bg_color);
7059

71-
let text_rects = text_rects(response.rect, self.selectables.len());
60+
let text_rects = text_rects(response.rect, this.selectables.len());
7261
let offset = text_rects[0].width();
7362

7463
let translation = ui.ctx().animate_value_with_time(
75-
self.id,
76-
offset * state.selected as f32,
64+
this.id,
65+
offset * *this.selected as f32,
7766
ANIMATION_TIME_SECONDS,
7867
);
7968
let selector_rect = text_rects[0].translate(vec2(translation, 0.0)).shrink(2.0);
8069
let selector_response =
81-
ui.interact(selector_rect, self.id.with("selector"), Sense::click());
70+
ui.interact(selector_rect, this.id.with("selector"), Sense::click());
8271
let selector_style = ui.style().interact(&selector_response);
8372
painter.rect_filled(selector_rect, rounding, selector_style.bg_fill);
8473

8574
let noninteractive_style = ui.style().noninteractive();
8675

87-
for (idx, (&rect, text)) in text_rects.iter().zip(self.selectables.iter()).enumerate() {
88-
let label_response = ui.interact(rect, self.id.with(idx), Sense::click());
76+
for (idx, (&rect, text)) in text_rects.iter().zip(this.selectables.iter()).enumerate() {
77+
let label_response = ui.interact(rect, this.id.with(idx), Sense::click());
8978
let style = ui.style().interact(&response);
9079

91-
let show_line = idx > 0 && state.selected != idx && state.selected + 1 != idx;
80+
let show_line = idx > 0 && *this.selected != idx && *this.selected + 1 != idx;
9281
{
9382
let animated_height = ui
9483
.ctx()
95-
.animate_bool(self.id.with("vline").with(idx), show_line);
84+
.animate_bool(this.id.with("vline").with(idx), show_line);
9685

9786
let height = vec2(0.0, rect.height() - 4.0);
9887
let center = rect.left_center();
@@ -107,7 +96,7 @@ impl<'ui, T: ToString> SegmentedControl<'ui, T> {
10796
}
10897

10998
if label_response.clicked() {
110-
state.selected = idx;
99+
*this.selected = idx;
111100
response.mark_changed();
112101
}
113102
painter.text(
@@ -122,24 +111,6 @@ impl<'ui, T: ToString> SegmentedControl<'ui, T> {
122111
}
123112
}
124113

125-
impl<T: ToString> Widget for SegmentedControl<'_, T> {
126-
fn ui(self, ui: &mut Ui) -> Response {
127-
let mut state = load_state(ui.ctx(), self.id);
128-
let response = self.show(ui, &mut state);
129-
save_state(ui.ctx(), self.id, state);
130-
response
131-
}
132-
}
133-
134-
fn load_state(ctx: &Context, id: Id) -> SegmentedControlState {
135-
let persisted = ctx.data_mut(|reader| reader.get_temp(id));
136-
persisted.unwrap_or_default()
137-
}
138-
139-
fn save_state(ctx: &Context, id: Id, state: SegmentedControlState) {
140-
ctx.data_mut(|writer| writer.insert_temp(id, state));
141-
}
142-
143114
fn text_rects(mut rect: Rect, number_of_texts: usize) -> Vec<Rect> {
144115
let base_width = rect.width() / number_of_texts as f32;
145116
let base_rect = {

tools/twix/src/panels/behavior_simulator.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,13 @@ impl Widget for &mut BehaviorSimulatorPanel {
109109
ui.add_space(50.0);
110110

111111
let robots = (1..=7).collect::<Vec<_>>();
112-
let robot_selection =
113-
SegmentedControl::new("robot-selector", &robots).ui(ui);
114-
self.selected_robot = *robot_selection.inner;
115-
if robot_selection.response.changed() {
112+
let response = SegmentedControl::new(
113+
"robot-selector",
114+
&mut self.selected_robot,
115+
&robots,
116+
)
117+
.ui(ui);
118+
if response.changed() {
116119
self.nao.write(
117120
"parameters.selected_robot",
118121
TextOrBinary::Text(self.selected_robot.into()),

tools/vista/src/app.rs

+14-28
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,27 @@
1-
use std::fmt::{Display, Error, Formatter};
2-
31
use eframe::{
42
egui::{
53
pos2, vec2, Align2, CentralPanel, Color32, FontId, Key, Modifiers, ScrollArea, Shape,
6-
TopBottomPanel,
4+
TopBottomPanel, Widget,
75
},
86
epaint::{PathStroke, QuadraticBezierShape},
97
App, CreationContext,
108
};
119

1210
use hulk_manifest::collect_hulk_cyclers;
11+
use hulk_widgets::SegmentedControl;
1312
use repository::Repository;
1413
use source_analyzer::{contexts::Field, cyclers::Cyclers};
1514

1615
pub struct DependencyInspector {
17-
_repository: Repository,
1816
cyclers: Cyclers,
1917
selected_cycler: usize,
2018
selected_node_index: Option<usize>,
2119
}
2220

23-
struct NamedIndex<'a> {
24-
index: usize,
25-
name: &'a str,
26-
}
27-
28-
impl Display for NamedIndex<'_> {
29-
fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), Error> {
30-
formatter.write_str(self.name)
31-
}
32-
}
33-
3421
impl DependencyInspector {
3522
pub fn new(_creation_context: &CreationContext, repository: Repository) -> Self {
3623
let cyclers = collect_hulk_cyclers(repository.crates_directory()).unwrap();
3724
Self {
38-
_repository: repository,
3925
cyclers,
4026
selected_cycler: 0,
4127
selected_node_index: None,
@@ -50,16 +36,12 @@ impl App for DependencyInspector {
5036
.cyclers
5137
.cyclers
5238
.iter()
53-
.enumerate()
54-
.map(|(index, cycler)| NamedIndex {
55-
index,
56-
name: &cycler.name,
57-
})
39+
.map(|cycler| &cycler.name)
5840
.collect();
5941
let response =
60-
hulk_widgets::SegmentedControl::new("cycler selector", &cycler_names).ui(ui);
61-
if response.response.changed() {
62-
self.selected_cycler = response.inner.index;
42+
SegmentedControl::new("cycler selector", &mut self.selected_cycler, &cycler_names)
43+
.ui(ui);
44+
if response.changed() {
6345
self.selected_node_index = None;
6446
}
6547
});
@@ -86,13 +68,17 @@ impl App for DependencyInspector {
8668
let mut node_selection_changed = false;
8769
ui.input_mut(|input| {
8870
if input.consume_key(Modifiers::NONE, Key::ArrowUp) {
89-
self.selected_node_index =
90-
Some(self.selected_node_index.unwrap_or(0).saturating_sub(1));
71+
self.selected_node_index = Some(match self.selected_node_index {
72+
Some(old) => old.saturating_sub(1),
73+
None => nodes.len() - 1,
74+
});
9175
node_selection_changed = true;
9276
}
9377
if input.consume_key(Modifiers::NONE, Key::ArrowDown) {
94-
self.selected_node_index =
95-
Some((self.selected_node_index.unwrap_or(0) + 1).min(nodes.len() - 1));
78+
self.selected_node_index = Some(match self.selected_node_index {
79+
Some(old) => (old + 1).min(nodes.len() - 1),
80+
None => 0,
81+
});
9682
node_selection_changed = true;
9783
}
9884
if input.consume_key(Modifiers::NONE, Key::Escape) {

tools/widget_gallery/src/main.rs

+11-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use eframe::{
2-
egui::{CentralPanel, Context},
2+
egui::{CentralPanel, Context, Widget},
33
run_native, App, Frame,
44
};
55
use hulk_widgets::{CompletionEdit, SegmentedControl};
@@ -15,15 +15,17 @@ fn main() -> eframe::Result {
1515
#[derive(Debug, Clone)]
1616
struct AppState {
1717
searchables: Vec<String>,
18-
selected: String,
18+
selected: usize,
19+
search_text: String,
1920
}
2021

2122
impl AppState {
2223
pub fn new() -> Self {
2324
let searchables: Vec<_> = (1..100).map(|x| x.to_string()).collect();
2425
Self {
2526
searchables,
26-
selected: String::new(),
27+
selected: 0,
28+
search_text: String::new(),
2729
}
2830
}
2931
}
@@ -35,10 +37,10 @@ impl App for AppState {
3537
let response = ui.add(CompletionEdit::new(
3638
"completion-edit",
3739
&self.searchables,
38-
&mut self.selected,
40+
&mut self.search_text,
3941
));
4042
if response.changed() {
41-
println!("Selected: {}", self.selected);
43+
println!("Selected: {}", self.search_text);
4244
}
4345

4446
if ui.button("Focus").clicked() {
@@ -51,10 +53,10 @@ impl App for AppState {
5153
ui.horizontal(|ui| {
5254
ui.columns(2, |columns| {
5355
let selectables = ["Dies", "Das", "Ananas", "Foo", "Bar", "Baz"];
54-
let selected = SegmentedControl::new("segmented-control", &selectables)
55-
.ui(&mut columns[0])
56-
.inner;
57-
columns[1].label(*selected);
56+
SegmentedControl::new("segmented-control", &mut self.selected, &selectables)
57+
.ui(&mut columns[0]);
58+
59+
columns[1].label(selectables[self.selected]);
5860
})
5961
})
6062
});

0 commit comments

Comments
 (0)