Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vista #1562

Merged
merged 15 commits into from
Mar 25, 2025
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -54,6 +54,7 @@ members = [
"tools/parameter_tester",
"tools/pepsi",
"tools/twix",
"tools/vista",
"tools/widget_gallery",
]
resolver = "2"
2 changes: 1 addition & 1 deletion crates/hulk/build.rs
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ use hulk_manifest::collect_hulk_cyclers;
use source_analyzer::{pretty::to_string_pretty, structs::Structs};

fn main() -> Result<()> {
let cyclers = collect_hulk_cyclers()?;
let cyclers = collect_hulk_cyclers("..")?;
for path in cyclers.watch_paths() {
println!("cargo:rerun-if-changed={}", path.display());
}
2 changes: 1 addition & 1 deletion crates/hulk_imagine/build.rs
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ use hulk_manifest::collect_hulk_cyclers;
use source_analyzer::{pretty::to_string_pretty, structs::Structs};

fn main() -> Result<()> {
let mut cyclers = collect_hulk_cyclers()?;
let mut cyclers = collect_hulk_cyclers("..")?;
cyclers
.cyclers
.retain(|cycler| cycler.name != "ObjectDetection");
5 changes: 2 additions & 3 deletions crates/hulk_manifest/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use std::time::Duration;
use std::{path::Path, time::Duration};

use source_analyzer::{
cyclers::{CyclerKind, Cyclers},
error::Error,
manifest::{CyclerManifest, FrameworkManifest},
};

pub fn collect_hulk_cyclers() -> Result<Cyclers, Error> {
pub fn collect_hulk_cyclers(root: impl AsRef<Path>) -> Result<Cyclers, Error> {
let manifest = FrameworkManifest {
cyclers: vec![
CyclerManifest {
@@ -136,6 +136,5 @@ pub fn collect_hulk_cyclers() -> Result<Cyclers, Error> {
],
};

let root = "..";
Cyclers::try_from_manifest(manifest, root)
}
2 changes: 1 addition & 1 deletion crates/hulk_replayer/build.rs
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ use source_analyzer::{pretty::to_string_pretty, structs::Structs};

fn main() -> Result<()> {
#[allow(unused_mut)] // must not be mut if "with_detection" feature is disabled
let mut cyclers = collect_hulk_cyclers()?;
let mut cyclers = collect_hulk_cyclers("..")?;
#[cfg(not(feature = "with_object_detection"))]
cyclers
.cyclers
73 changes: 22 additions & 51 deletions crates/hulk_widgets/src/segmented_control.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
use egui::{
vec2, Align2, Context, CornerRadius, Id, InnerResponse, Key, Rect, Response, Sense, TextStyle,
Ui, Widget,
};
use egui::{vec2, Align2, CornerRadius, Id, Key, Rect, Response, 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],
corner_radius: Option<CornerRadius>,
text_style: TextStyle,
}

#[derive(Debug, Default, Clone)]
struct SegmentedControlState {
selected: usize,
}

impl<'ui, T: ToString> SegmentedControl<'ui, T> {
pub fn new(id: impl Into<Id>, selectables: &'ui [T]) -> Self {
pub fn new(id: impl Into<Id>, selected: &'ui mut usize, selectables: &'ui [T]) -> Self {
SegmentedControl {
id: id.into(),
selected,
selectables,
corner_radius: None,
text_style: TextStyle::Body,
@@ -31,25 +25,20 @@ impl<'ui, T: ToString> SegmentedControl<'ui, T> {
self.corner_radius = Some(corner_radius.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<T: ToString> 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 corner_radius = self
let corner_radius = this
.corner_radius
.unwrap_or(ui.style().noninteractive().corner_radius);

@@ -58,10 +47,10 @@ 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();
}
})
@@ -72,31 +61,31 @@ impl<'ui, T: ToString> SegmentedControl<'ui, T> {
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, corner_radius, 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();
@@ -111,7 +100,7 @@ impl<'ui, T: ToString> SegmentedControl<'ui, T> {
}

if label_response.clicked() {
state.selected = idx;
*this.selected = idx;
response.mark_changed();
}
painter.text(
@@ -126,24 +115,6 @@ impl<'ui, T: ToString> SegmentedControl<'ui, T> {
}
}

impl<T: ToString> 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<Rect> {
let base_width = rect.width() / number_of_texts as f32;
let base_rect = {
1 change: 1 addition & 0 deletions tools/pepsi/src/cargo.rs
Original file line number Diff line number Diff line change
@@ -187,6 +187,7 @@ async fn resolve_manifest_path(
Some("parameter_tester") => repository.root.join("tools/parameter_tester/Cargo.toml"),
Some("pepsi") => repository.root.join("tools/pepsi/Cargo.toml"),
Some("twix") => repository.root.join("tools/twix/Cargo.toml"),
Some("vista") => repository.root.join("tools/vista/Cargo.toml"),
Some("widget_gallery") => repository.root.join("tools/widget_gallery/Cargo.toml"),

_ => compose_manifest_path(manifest).await.wrap_err_with(|| {
11 changes: 7 additions & 4 deletions tools/twix/src/panels/behavior_simulator.rs
Original file line number Diff line number Diff line change
@@ -109,10 +109,13 @@ impl Widget for &mut BehaviorSimulatorPanel {
ui.add_space(50.0);

let robots = (1..=7).collect::<Vec<_>>();
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()),
14 changes: 14 additions & 0 deletions tools/vista/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "vista"
version.workspace = true
edition.workspace = true
license.workspace = true
homepage.workspace = true

[dependencies]
eframe.workspace = true
hulk_manifest.workspace = true
hulk_widgets.workspace = true
repository.workspace = true
source_analyzer.workspace = true
tokio.workspace = true
Loading