From adb2728deb5a5e97676cd6884678638fc1f4fb8f Mon Sep 17 00:00:00 2001
From: Federico Lolli <federico.lolli@skywarder.eu>
Date: Thu, 10 Apr 2025 19:55:47 +0200
Subject: [PATCH] WIP

---
 Cargo.lock                                    |  12 +-
 Cargo.toml                                    |   1 +
 src/ui/panes/valve_control.rs                 |  82 +--
 src/ui/panes/valve_control/ui.rs              |   2 +-
 .../panes/valve_control/ui/shortcut_widget.rs |   9 +-
 .../valve_control/ui/valve_control_window.rs  | 544 ++++++++++++++----
 6 files changed, 489 insertions(+), 161 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index f65be72..28368be 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1050,7 +1050,7 @@ checksum = "67756b63b283a65bd0534b0c2a5fb1a12a5768bb6383d422147cc93193d09cfc"
 dependencies = [
  "ahash",
  "egui",
- "itertools",
+ "itertools 0.13.0",
  "log",
  "serde",
 ]
@@ -1827,6 +1827,15 @@ dependencies = [
  "either",
 ]
 
+[[package]]
+name = "itertools"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
+dependencies = [
+ "either",
+]
+
 [[package]]
 name = "itoa"
 version = "1.0.15"
@@ -3059,6 +3068,7 @@ dependencies = [
  "egui_tiles",
  "enum_dispatch",
  "glam",
+ "itertools 0.14.0",
  "mavlink-bindgen",
  "mint",
  "profiling",
diff --git a/Cargo.toml b/Cargo.toml
index 8c2de1b..b049c64 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,6 +21,7 @@ egui_plot = "0.31"
 egui_file = "0.22"
 enum_dispatch = "0.3"
 glam = { version = "0.29", features = ["serde", "mint"] }
+itertools = "0.14.0"
 mint = "0.5.9"
 profiling = "1.0"
 ring-channel = "0.12.0"
diff --git a/src/ui/panes/valve_control.rs b/src/ui/panes/valve_control.rs
index 73289d7..ed6743b 100644
--- a/src/ui/panes/valve_control.rs
+++ b/src/ui/panes/valve_control.rs
@@ -33,7 +33,7 @@ use super::PaneBehavior;
 
 use commands::CommandSM;
 use icons::Icon;
-use ui::{ShortcutCard, ValveControlWindow, map_key_to_shortcut};
+use ui::{ShortcutCard, ValveControlView, map_key_to_shortcut};
 use valves::{Valve, ValveStateManager};
 
 const DEFAULT_AUTO_REFRESH_RATE: Duration = Duration::from_secs(1);
@@ -84,7 +84,7 @@ pub struct ValveControlPane {
     #[serde(skip)]
     valve_key_map: HashMap<Valve, Key>,
     #[serde(skip)]
-    valve_window: Option<ValveControlWindow>,
+    valve_view: Option<ValveControlView>,
 }
 
 impl Default for ValveControlPane {
@@ -101,7 +101,7 @@ impl Default for ValveControlPane {
             last_refresh: None,
             is_settings_window_open: false,
             valve_key_map,
-            valve_window: None,
+            valve_view: None,
         }
     }
 }
@@ -114,48 +114,48 @@ impl PaneBehavior for ValveControlPane {
         // Set this to at least double the maximum icon size used
         Icon::init_cache(ui.ctx(), (100, 100));
 
-        let res = ui
-            .scope_builder(UiBuilder::new().sense(Sense::click_and_drag()), |ui| {
-                self.pane_ui()(ui);
-                ui.allocate_space(ui.available_size());
-            })
-            .response;
-
-        // Show the menu when the user right-clicks the pane
-        res.context_menu(self.menu_ui());
-
-        // Check if the user started dragging the pane
-        if res.drag_started() {
-            pane_response.set_drag_started();
-        }
-
-        // capture actions from keyboard shortcuts
-        let action = self.keyboard_actions(shortcut_handler);
+        if let Some(valve_view) = &mut self.valve_view {
+            if let Some(command) = valve_view.ui(ui, shortcut_handler) {
+                self.commands.push(command.into());
+            }
 
-        match action {
-            // Open the valve control window if the action is to open it
-            Some(PaneAction::OpenValveControl(valve)) => {
-                self.valve_window.replace(ValveControlWindow::new(valve));
+            if valve_view.is_closed() {
+                self.valve_view = None;
+            }
+        } else {
+            let res = ui
+                .scope_builder(UiBuilder::new().sense(Sense::click_and_drag()), |ui| {
+                    self.pane_ui()(ui);
+                    ui.allocate_space(ui.available_size());
+                })
+                .response;
+
+            // Show the menu when the user right-clicks the pane
+            res.context_menu(self.menu_ui());
+
+            // Check if the user started dragging the pane
+            if res.drag_started() {
+                pane_response.set_drag_started();
             }
-            None => {}
-        }
 
-        Window::new("Settings")
-            .id(ui.auto_id_with("settings"))
-            .auto_sized()
-            .collapsible(true)
-            .movable(true)
-            .open(&mut self.is_settings_window_open)
-            .show(ui.ctx(), Self::settings_window_ui(&mut self.auto_refresh));
+            // capture actions from keyboard shortcuts
+            let action = self.keyboard_actions(shortcut_handler);
 
-        if let Some(valve_window) = &mut self.valve_window {
-            if let Some(command) = valve_window.ui(ui, shortcut_handler) {
-                self.commands.push(command.into());
+            match action {
+                // Open the valve control window if the action is to open it
+                Some(PaneAction::OpenValveControl(valve)) => {
+                    self.valve_view.replace(ValveControlView::new(valve));
+                }
+                None => {}
             }
 
-            if valve_window.is_closed() {
-                self.valve_window = None;
-            }
+            Window::new("Settings")
+                .id(ui.auto_id_with("settings"))
+                .auto_sized()
+                .collapsible(true)
+                .movable(true)
+                .open(&mut self.is_settings_window_open)
+                .show(ui.ctx(), Self::settings_window_ui(&mut self.auto_refresh));
         }
 
         pane_response
@@ -241,7 +241,7 @@ impl ValveControlPane {
 
                             if response.clicked() {
                                 info!("Clicked on valve: {:?}", valve);
-                                self.valve_window = Some(ValveControlWindow::new(valve));
+                                self.valve_view = Some(ValveControlView::new(valve));
                             }
                         }
                         ui.end_row();
@@ -381,7 +381,7 @@ impl ValveControlPane {
                     let visuals = ui.style().interact(&response);
 
                     let (fill_color, btn_fill_color, stroke) = if response.clicked()
-                        || shortcut_key_is_down && self.valve_window.is_none()
+                        || shortcut_key_is_down && self.valve_view.is_none()
                     {
                         let visuals = ui.visuals().widgets.active;
                         (visuals.bg_fill, visuals.bg_fill, visuals.bg_stroke)
diff --git a/src/ui/panes/valve_control/ui.rs b/src/ui/panes/valve_control/ui.rs
index 2c5a1d3..6350f61 100644
--- a/src/ui/panes/valve_control/ui.rs
+++ b/src/ui/panes/valve_control/ui.rs
@@ -6,7 +6,7 @@ use egui::{Key, KeyboardShortcut, Modifiers};
 // Re-export the modules for the UI modules
 use super::{commands, icons, valves};
 
-pub use {shortcut_widget::ShortcutCard, valve_control_window::ValveControlWindow};
+pub use {shortcut_widget::ShortcutCard, valve_control_window::ValveControlView};
 
 #[inline]
 pub fn map_key_to_shortcut(key: Key) -> KeyboardShortcut {
diff --git a/src/ui/panes/valve_control/ui/shortcut_widget.rs b/src/ui/panes/valve_control/ui/shortcut_widget.rs
index 9bbc056..212d895 100644
--- a/src/ui/panes/valve_control/ui/shortcut_widget.rs
+++ b/src/ui/panes/valve_control/ui/shortcut_widget.rs
@@ -6,6 +6,7 @@ use egui::{
 pub struct ShortcutCard {
     shortcut: KeyboardShortcut,
     text_size: f32,
+    margin: Margin,
     text_color: Option<Color32>,
     fill_color: Option<Color32>,
 }
@@ -30,7 +31,7 @@ impl Widget for ShortcutCard {
         Frame::canvas(ui.style())
             .fill(fill_color)
             .stroke(Stroke::NONE)
-            .inner_margin(Margin::same(5))
+            .inner_margin(self.margin)
             .corner_radius(corner_radius)
             .show(ui, |ui| {
                 Label::new(number).selectable(false).ui(ui);
@@ -44,6 +45,7 @@ impl ShortcutCard {
         Self {
             shortcut,
             text_size: 20.,
+            margin: Margin::same(5),
             text_color: None,
             fill_color: None,
         }
@@ -63,4 +65,9 @@ impl ShortcutCard {
         self.fill_color = Some(fill_color);
         self
     }
+
+    pub fn margin(mut self, margin: Margin) -> Self {
+        self.margin = margin;
+        self
+    }
 }
diff --git a/src/ui/panes/valve_control/ui/valve_control_window.rs b/src/ui/panes/valve_control/ui/valve_control_window.rs
index 13a2642..5fa463d 100644
--- a/src/ui/panes/valve_control/ui/valve_control_window.rs
+++ b/src/ui/panes/valve_control/ui/valve_control_window.rs
@@ -1,7 +1,8 @@
 use egui::{
-    Color32, DragValue, Frame, Key, Label, Modal, Modifiers, Response, RichText, Sense, Stroke, Ui,
-    UiBuilder, Vec2, Widget,
+    Align, Button, Color32, Direction, DragValue, FontId, Frame, Grid, Key, Label, Layout, Margin,
+    Modifiers, Response, RichText, Sense, Stroke, TextEdit, Ui, UiBuilder, Vec2, Widget,
 };
+use egui_extras::{Size, Strip, StripBuilder};
 use tracing::info;
 
 use crate::ui::shortcuts::{ShortcutHandler, ShortcutMode};
@@ -12,29 +13,33 @@ use super::{
 };
 
 const WIGGLE_KEY: Key = Key::Minus;
-const TIMING_KEY: Key = Key::Slash;
-const APERTURE_KEY: Key = Key::Period;
+/// Key used to focus on the aperture field
+const FOCUS_APERTURE_KEY: Key = Key::Num1;
+/// Key used to focus on the timing field
+const FOCUS_TIMING_KEY: Key = Key::Num2;
+/// Key used to set the parameter and loose focus on the field
+const SET_PAR_KEY: Key = Key::Plus;
 
 #[derive(Debug, Clone, PartialEq)]
-pub struct ValveControlWindow {
+pub struct ValveControlView {
     valve: Valve,
-    state: ValveWindowState,
+    state: ValveViewState,
     timing_ms: u32,
     aperture_perc: f32,
 }
 
-impl ValveControlWindow {
-    pub fn new(valve: Valve) -> ValveControlWindow {
-        ValveControlWindow {
+impl ValveControlView {
+    pub fn new(valve: Valve) -> ValveControlView {
+        ValveControlView {
             valve,
-            state: ValveWindowState::Open,
+            state: ValveViewState::Open,
             timing_ms: 0,
             aperture_perc: 0.0,
         }
     }
 
     pub fn is_closed(&self) -> bool {
-        matches!(self.state, ValveWindowState::Closed)
+        matches!(self.state, ValveViewState::Closed)
     }
 
     #[profiling::function]
@@ -47,27 +52,26 @@ impl ValveControlWindow {
         // Capture the keyboard shortcuts
         let mut action = self.keyboard_actions(shortcut_handler);
 
-        // Draw the window UI
-        Modal::new(ui.auto_id_with("valve_control"))
-            .show(ui.ctx(), self.draw_window_ui(&mut action));
+        // Draw the view inside the pane
+        ui.scope(self.draw_view_ui(&mut action));
 
         // Handle the actions
         self.handle_actions(action)
     }
 
-    fn draw_window_ui(&mut self, action: &mut Option<WindowAction>) -> impl FnOnce(&mut Ui) {
+    fn draw_view_ui(&mut self, action: &mut Option<WindowAction>) -> impl FnOnce(&mut Ui) {
         |ui: &mut Ui| {
-            let icon_size = Vec2::splat(25.);
-            let text_size = 16.;
+            let icon_size = Vec2::splat(20.);
+            let text_size = 14.;
 
             fn btn_ui<R>(
-                window_state: &ValveWindowState,
+                window_state: &ValveViewState,
                 key: Key,
                 add_contents: impl FnOnce(&mut Ui) -> R,
             ) -> impl FnOnce(&mut Ui) -> Response {
                 move |ui| {
-                    let wiggle_btn = Frame::canvas(ui.style())
-                        .inner_margin(ui.spacing().menu_margin)
+                    let btn = Frame::canvas(ui.style())
+                        .inner_margin(Margin::same(4))
                         .corner_radius(ui.visuals().noninteractive().corner_radius);
 
                     ui.scope_builder(UiBuilder::new().id_salt(key).sense(Sense::click()), |ui| {
@@ -88,12 +92,11 @@ impl ValveControlWindow {
                                 (visuals.bg_fill.gamma_multiply(0.3), stroke)
                             };
 
-                        wiggle_btn
-                            .fill(fill_color)
+                        btn.fill(fill_color)
                             .stroke(stroke)
                             .stroke(stroke)
                             .show(ui, |ui| {
-                                ui.set_width(200.);
+                                ui.set_width(ui.available_width());
                                 ui.horizontal(|ui| add_contents(ui))
                             });
 
@@ -105,84 +108,391 @@ impl ValveControlWindow {
                 }
             }
 
-            let wiggle_btn_response = btn_ui(&self.state, WIGGLE_KEY, |ui| {
-                ShortcutCard::new(map_key_to_shortcut(WIGGLE_KEY))
-                    .text_color(ui.visuals().text_color())
-                    .fill_color(ui.visuals().widgets.inactive.bg_fill)
-                    .text_size(20.)
-                    .ui(ui);
-                ui.add(
-                    Icon::Wiggle
-                        .as_image(ui.ctx().theme())
-                        .fit_to_exact_size(icon_size),
-                );
-                ui.add(Label::new(RichText::new("Wiggle").size(text_size)).selectable(false));
-            })(ui);
-
-            let aperture_btn_response = btn_ui(&self.state, APERTURE_KEY, |ui| {
-                ShortcutCard::new(map_key_to_shortcut(APERTURE_KEY))
-                    .text_color(ui.visuals().text_color())
-                    .fill_color(ui.visuals().widgets.inactive.bg_fill)
-                    .text_size(20.)
-                    .ui(ui);
-                ui.add(
-                    Icon::Aperture
-                        .as_image(ui.ctx().theme())
-                        .fit_to_exact_size(icon_size),
-                );
-                ui.add(Label::new(RichText::new("Aperture: ").size(text_size)).selectable(false));
-                let drag_value_id = ui.next_auto_id();
-                ui.add(
-                    DragValue::new(&mut self.aperture_perc)
-                        .speed(0.5)
-                        .range(0.0..=100.0)
-                        .fixed_decimals(0)
-                        .update_while_editing(false)
-                        .suffix("%"),
-                );
-                if matches!(&self.state, ValveWindowState::ApertureFocused) {
-                    ui.ctx().memory_mut(|m| {
-                        m.request_focus(drag_value_id);
-                    });
+            let valid_fill = ui
+                .visuals()
+                .widgets
+                .inactive
+                .bg_fill
+                .lerp_to_gamma(Color32::GREEN, 0.3);
+            let invalid_fill = ui
+                .visuals()
+                .widgets
+                .inactive
+                .bg_fill
+                .lerp_to_gamma(Color32::RED, 0.3);
+
+            fn shortcut_ui(ui: &Ui, key: &Key) -> ShortcutCard {
+                let vis = ui.visuals();
+                ShortcutCard::new(map_key_to_shortcut(*key))
+                    .text_color(vis.strong_text_color())
+                    .fill_color(vis.gray_out(vis.widgets.inactive.bg_fill))
+                    .margin(Margin::symmetric(5, 2))
+                    .text_size(12.)
+            }
+
+            fn add_parameter_btn(ui: &mut Ui, key: Key) -> Response {
+                ui.scope_builder(UiBuilder::new().id_salt(key).sense(Sense::click()), |ui| {
+                    Frame::canvas(ui.style())
+                        .inner_margin(Margin::symmetric(4, 2))
+                        .outer_margin(0)
+                        .corner_radius(ui.visuals().noninteractive().corner_radius)
+                        .fill(ui.visuals().widgets.inactive.bg_fill)
+                        .stroke(Stroke::new(1., Color32::TRANSPARENT))
+                        .show(ui, |ui| {
+                            ui.set_height(ui.available_height());
+                            ui.horizontal_centered(|ui| {
+                                ui.set_height(21.);
+                                ui.add_space(1.);
+                                Label::new(
+                                    RichText::new("SET")
+                                        .size(16.)
+                                        .color(ui.visuals().widgets.inactive.text_color()),
+                                )
+                                .selectable(false)
+                                .ui(ui);
+                                shortcut_ui(ui, &key).ui(ui);
+                            });
+                        });
+                })
+                .response
+            }
+
+            // set aperture and timing buttons
+            let aperture_btn: Box<dyn FnOnce(&mut Ui) -> Response> = match self.state {
+                ValveViewState::Open => Box::new(|ui| add_parameter_btn(ui, FOCUS_APERTURE_KEY)),
+                ValveViewState::ApertureFocused => {
+                    Box::new(|ui| add_parameter_btn(ui, SET_PAR_KEY))
                 }
-            })(ui);
-
-            let timing_btn_response = btn_ui(&self.state, TIMING_KEY, |ui| {
-                ShortcutCard::new(map_key_to_shortcut(TIMING_KEY))
-                    .text_color(ui.visuals().text_color())
-                    .fill_color(ui.visuals().widgets.inactive.bg_fill)
-                    .text_size(20.)
-                    .ui(ui);
-                ui.add(
-                    Icon::Timing
-                        .as_image(ui.ctx().theme())
-                        .fit_to_exact_size(icon_size),
-                );
-                ui.add(Label::new(RichText::new("Timing: ").size(text_size)).selectable(false));
-                let drag_value_id = ui.next_auto_id();
-                ui.add(
-                    DragValue::new(&mut self.timing_ms)
-                        .speed(1)
-                        .range(1..=10000)
-                        .fixed_decimals(0)
-                        .update_while_editing(false)
-                        .suffix(" [ms]"),
+                ValveViewState::TimingFocused | ValveViewState::Closed => {
+                    Box::new(|ui| ui.response())
+                }
+            };
+
+            // set timing button
+            let timing_btn: Box<dyn FnOnce(&mut Ui) -> Response> = match self.state {
+                ValveViewState::Open => Box::new(|ui| add_parameter_btn(ui, FOCUS_TIMING_KEY)),
+                ValveViewState::TimingFocused => Box::new(|ui| add_parameter_btn(ui, SET_PAR_KEY)),
+                ValveViewState::ApertureFocused | ValveViewState::Closed => {
+                    Box::new(|ui| ui.response())
+                }
+            };
+
+            // wiggle button with shortcut
+            let wiggle_btn = |ui: &mut Ui| {
+                ui.scope_builder(
+                    UiBuilder::new().id_salt(WIGGLE_KEY).sense(Sense::click()),
+                    |ui| {
+                        Frame::canvas(ui.style())
+                            .inner_margin(Margin::symmetric(4, 2))
+                            .outer_margin(0)
+                            .corner_radius(ui.visuals().noninteractive().corner_radius)
+                            .fill(ui.visuals().widgets.inactive.bg_fill)
+                            .stroke(Stroke::new(1., Color32::TRANSPARENT))
+                            .show(ui, |ui| {
+                                ui.set_height(ui.available_height());
+                                ui.horizontal_centered(|ui| {
+                                    ui.set_height(21.);
+                                    ui.add_space(1.);
+                                    Label::new(
+                                        RichText::new("WIGGLE")
+                                            .size(16.)
+                                            .color(ui.visuals().widgets.inactive.text_color()),
+                                    )
+                                    .selectable(false)
+                                    .ui(ui);
+                                    ui.add(
+                                        Icon::Wiggle
+                                            .as_image(ui.ctx().theme())
+                                            .fit_to_exact_size(Vec2::splat(22.)),
+                                    );
+                                    shortcut_ui(ui, &WIGGLE_KEY).ui(ui);
+                                });
+                            });
+                    },
                 );
-                if matches!(&self.state, ValveWindowState::TimingFocused) {
-                    ui.ctx().memory_mut(|m| {
-                        m.request_focus(drag_value_id);
+            };
+
+            ui.with_layout(Layout::centered_and_justified(Direction::TopDown), |ui| {
+                ui.set_max_width(300.);
+                ui.set_min_height(50.);
+                StripBuilder::new(ui)
+                    .size(Size::exact(5.))
+                    .sizes(Size::initial(5.), 3)
+                    .vertical(|mut strip| {
+                        strip.empty();
+                        // strip.cell(|ui| {
+                        //     // ui.add_sized(
+                        //     //     Vec2::new(ui.available_width(), 0.0),
+                        //     //     Button::new("Wiggle"),
+                        //     // );
+                        //     let wiggle_btn_response = btn_ui(&self.state, WIGGLE_KEY, |ui| {
+                        //         shortcut_ui(ui, &WIGGLE_KEY).ui(ui);
+                        //         ui.add(
+                        //             Icon::Wiggle
+                        //                 .as_image(ui.ctx().theme())
+                        //                 .fit_to_exact_size(icon_size),
+                        //         );
+                        //         ui.add(
+                        //             Label::new(RichText::new("WIGGLE").size(text_size))
+                        //                 .selectable(false),
+                        //         );
+                        //     })(ui);
+                        // });
+                        strip.strip(|builder| {
+                            builder
+                                // .size(Size::exact(230.))
+                                .size(Size::initial(10.))
+                                .size(Size::exact(25.))
+                                .horizontal(|mut strip| {
+                                    strip.strip(|builder| {
+                                        builder
+                                            .size(Size::remainder())
+                                            .size(Size::initial(5.))
+                                            .size(Size::remainder())
+                                            .vertical(|mut strip| {
+                                                strip.empty();
+                                                strip.cell(|ui| {
+                                                    ui.with_layout(
+                                                        Layout::right_to_left(Align::Min),
+                                                        |ui| {
+                                                            Label::new(
+                                                                RichText::new(
+                                                                    self.valve
+                                                                        .to_string()
+                                                                        .to_uppercase(),
+                                                                )
+                                                                .color(
+                                                                    ui.visuals()
+                                                                        .strong_text_color(),
+                                                                )
+                                                                .size(16.),
+                                                            )
+                                                            .ui(ui);
+                                                            Label::new(
+                                                                RichText::new("VALVE: ").size(16.),
+                                                            )
+                                                            .selectable(false)
+                                                            .ui(ui);
+                                                        },
+                                                    );
+                                                });
+                                                strip.empty();
+                                            });
+                                    });
+                                    strip.cell(wiggle_btn);
+                                });
+                        });
+                        strip.strip(|builder| {
+                            builder
+                                .sizes(Size::initial(85.), 4)
+                                .horizontal(|mut strip| {
+                                    strip.strip(|builder| {
+                                        builder
+                                            .size(Size::remainder())
+                                            .size(Size::initial(5.))
+                                            .size(Size::remainder())
+                                            .vertical(|mut strip| {
+                                                strip.empty();
+                                                strip.cell(|ui| {
+                                                    ui.with_layout(
+                                                        Layout::right_to_left(Align::Min),
+                                                        |ui| {
+                                                            Label::new(
+                                                                RichText::new("APERTURE:")
+                                                                    .size(16.),
+                                                            )
+                                                            .selectable(false)
+                                                            .ui(ui);
+                                                        },
+                                                    );
+                                                });
+                                                strip.empty();
+                                            });
+                                    });
+                                    strip.cell(|ui| {
+                                        Frame::canvas(ui.style())
+                                            .outer_margin(0)
+                                            .inner_margin(Margin::symmetric(0, 3))
+                                            .corner_radius(
+                                                ui.visuals().noninteractive().corner_radius,
+                                            )
+                                            .fill(invalid_fill)
+                                            .stroke(Stroke::new(1., Color32::TRANSPARENT))
+                                            .show(ui, |ui| {
+                                                Label::new(
+                                                    RichText::new("0.813").size(14.).strong(),
+                                                )
+                                                .ui(ui);
+                                            });
+                                    });
+                                    strip.cell(|ui| {
+                                        Frame::canvas(ui.style())
+                                            .inner_margin(Margin::symmetric(0, 3))
+                                            .outer_margin(0)
+                                            .corner_radius(
+                                                ui.visuals().noninteractive().corner_radius,
+                                            )
+                                            .fill(ui.visuals().widgets.inactive.bg_fill)
+                                            .stroke(Stroke::new(1., Color32::TRANSPARENT))
+                                            .show(ui, |ui| {
+                                                let res = ui.add_sized(
+                                                    Vec2::new(ui.available_width(), 0.0),
+                                                    DragValue::new(&mut self.aperture_perc)
+                                                        .speed(0.5)
+                                                        .range(0.0..=100.0)
+                                                        .fixed_decimals(0)
+                                                        .update_while_editing(false)
+                                                        .suffix("%"),
+                                                );
+                                                if res.gained_focus() {
+                                                    self.state = ValveViewState::ApertureFocused;
+                                                }
+                                            });
+                                    });
+                                    strip.cell(|ui| {
+                                        aperture_btn(ui);
+                                    });
+                                });
+                        });
+                        strip.strip(|builder| {
+                            builder
+                                .sizes(Size::initial(85.), 4)
+                                .horizontal(|mut strip| {
+                                    strip.strip(|builder| {
+                                        builder
+                                            .size(Size::remainder())
+                                            .size(Size::initial(10.))
+                                            .size(Size::remainder())
+                                            .vertical(|mut strip| {
+                                                strip.empty();
+                                                strip.cell(|ui| {
+                                                    ui.with_layout(
+                                                        Layout::right_to_left(Align::Min),
+                                                        |ui| {
+                                                            Label::new(
+                                                                RichText::new("TIMING:").size(16.),
+                                                            )
+                                                            .selectable(false)
+                                                            .ui(ui);
+                                                        },
+                                                    );
+                                                });
+                                                strip.empty();
+                                            });
+                                    });
+                                    strip.cell(|ui| {
+                                        Frame::canvas(ui.style())
+                                            .inner_margin(Margin::same(4))
+                                            .corner_radius(
+                                                ui.visuals().noninteractive().corner_radius,
+                                            )
+                                            .fill(valid_fill)
+                                            .stroke(Stroke::new(1., Color32::TRANSPARENT))
+                                            .show(ui, |ui| {
+                                                Label::new(
+                                                    RichText::new("650ms").size(14.).strong(),
+                                                )
+                                                .ui(ui);
+                                            });
+                                    });
+                                    strip.cell(|ui| {
+                                        Frame::canvas(ui.style())
+                                            .inner_margin(Margin::same(4))
+                                            .corner_radius(
+                                                ui.visuals().noninteractive().corner_radius,
+                                            )
+                                            .fill(ui.visuals().widgets.inactive.bg_fill)
+                                            .stroke(Stroke::new(1., Color32::TRANSPARENT))
+                                            .show(ui, |ui| {
+                                                ui.add_sized(
+                                                    Vec2::new(ui.available_width(), 0.0),
+                                                    DragValue::new(&mut self.timing_ms)
+                                                        .speed(1)
+                                                        .range(1..=10000)
+                                                        .fixed_decimals(0)
+                                                        .update_while_editing(false)
+                                                        .suffix(" [ms]"),
+                                                );
+                                            });
+                                    });
+                                    strip.cell(|ui| {
+                                        timing_btn(ui);
+                                    });
+                                });
+                        });
                     });
-                }
-            })(ui);
-
-            // consider that action may be different that null if a keyboard shortcut was captured
-            if wiggle_btn_response.clicked() {
-                action.replace(WindowAction::Wiggle);
-            } else if aperture_btn_response.clicked() {
-                action.replace(WindowAction::SetAperture);
-            } else if timing_btn_response.clicked() {
-                action.replace(WindowAction::SetTiming);
-            }
+            });
+
+            // ui.horizontal(|ui| {
+            //     let wiggle_btn_response = btn_ui(&self.state, WIGGLE_KEY, |ui| {
+            //         shortcut_ui(ui, &WIGGLE_KEY).ui(ui);
+            //         ui.add(
+            //             Icon::Wiggle
+            //                 .as_image(ui.ctx().theme())
+            //                 .fit_to_exact_size(icon_size),
+            //         );
+            //         ui.add(Label::new(RichText::new("Wiggle").size(text_size)).selectable(false));
+            //     })(ui);
+
+            //     let aperture_btn_response = btn_ui(&self.state, APERTURE_KEY, |ui| {
+            //         shortcut_ui(ui, &APERTURE_KEY).ui(ui);
+            //         ui.add(
+            //             Icon::Aperture
+            //                 .as_image(ui.ctx().theme())
+            //                 .fit_to_exact_size(icon_size),
+            //         );
+            //         ui.add(
+            //             Label::new(RichText::new("Aperture: ").size(text_size)).selectable(false),
+            //         );
+            //         let drag_value_id = ui.next_auto_id();
+            //         ui.add(
+            //             DragValue::new(&mut self.aperture_perc)
+            //                 .speed(0.5)
+            //                 .range(0.0..=100.0)
+            //                 .fixed_decimals(0)
+            //                 .update_while_editing(false)
+            //                 .suffix("%"),
+            //         );
+            //         if matches!(&self.state, ValveViewState::ApertureFocused) {
+            //             ui.ctx().memory_mut(|m| {
+            //                 m.request_focus(drag_value_id);
+            //             });
+            //         }
+            //     })(ui);
+
+            //     let timing_btn_response = btn_ui(&self.state, TIMING_KEY, |ui| {
+            //         shortcut_ui(ui, &TIMING_KEY).ui(ui);
+            //         ui.add(
+            //             Icon::Timing
+            //                 .as_image(ui.ctx().theme())
+            //                 .fit_to_exact_size(icon_size),
+            //         );
+            //         ui.add(Label::new(RichText::new("Timing: ").size(text_size)).selectable(false));
+            //         let drag_value_id = ui.next_auto_id();
+            //         ui.add(
+            //             DragValue::new(&mut self.timing_ms)
+            //                 .speed(1)
+            //                 .range(1..=10000)
+            //                 .fixed_decimals(0)
+            //                 .update_while_editing(false)
+            //                 .suffix(" [ms]"),
+            //         );
+            //         if matches!(&self.state, ValveViewState::TimingFocused) {
+            //             ui.ctx().memory_mut(|m| {
+            //                 m.request_focus(drag_value_id);
+            //             });
+            //         }
+            //     })(ui);
+
+            //     // consider that action may be different that null if a keyboard shortcut was captured
+            //     if wiggle_btn_response.clicked() {
+            //         action.replace(WindowAction::Wiggle);
+            //     } else if aperture_btn_response.clicked() {
+            //         action.replace(WindowAction::SetAperture);
+            //     } else if timing_btn_response.clicked() {
+            //         action.replace(WindowAction::SetTiming);
+            //     }
+            // });
         }
     }
 
@@ -190,11 +500,11 @@ impl ValveControlWindow {
         match action {
             // If the action close is called, close the window
             Some(WindowAction::CloseWindow) => {
-                self.state = ValveWindowState::Closed;
+                self.state = ValveViewState::Closed;
                 None
             }
             Some(WindowAction::LooseFocus) => {
-                self.state = ValveWindowState::Open;
+                self.state = ValveViewState::Open;
                 None
             }
             Some(WindowAction::Wiggle) => {
@@ -206,7 +516,7 @@ impl ValveControlWindow {
                     "Issued command to set timing for valve {:?} to {} ms",
                     self.valve, self.timing_ms
                 );
-                self.state = ValveWindowState::Open;
+                self.state = ValveViewState::Open;
                 Some(Command::set_atomic_valve_timing(self.valve, self.timing_ms))
             }
             Some(WindowAction::SetAperture) => {
@@ -214,18 +524,18 @@ impl ValveControlWindow {
                     "Issued command to set aperture for valve {:?} to {}%",
                     self.valve, self.aperture_perc
                 );
-                self.state = ValveWindowState::Open;
+                self.state = ValveViewState::Open;
                 Some(Command::set_valve_maximum_aperture(
                     self.valve,
                     self.aperture_perc / 100.,
                 ))
             }
             Some(WindowAction::FocusOnTiming) => {
-                self.state = ValveWindowState::TimingFocused;
+                self.state = ValveViewState::TimingFocused;
                 None
             }
             Some(WindowAction::FocusOnAperture) => {
-                self.state = ValveWindowState::ApertureFocused;
+                self.state = ValveViewState::ApertureFocused;
                 None
             }
             _ => None,
@@ -233,49 +543,49 @@ impl ValveControlWindow {
     }
 }
 
-impl ValveControlWindow {
+impl ValveControlView {
     #[profiling::function]
     fn keyboard_actions(&self, shortcut_handler: &mut ShortcutHandler) -> Option<WindowAction> {
         let mut key_action_pairs = Vec::new();
 
         shortcut_handler.activate_mode(ShortcutMode::valve_control());
         match self.state {
-            ValveWindowState::Open => {
+            ValveViewState::Open => {
                 // A window is open, so we can map the keys to control the valve
                 key_action_pairs.push((Modifiers::NONE, WIGGLE_KEY, WindowAction::Wiggle));
-                key_action_pairs.push((Modifiers::NONE, TIMING_KEY, WindowAction::FocusOnTiming));
-                key_action_pairs.push((
-                    Modifiers::NONE,
-                    APERTURE_KEY,
-                    WindowAction::FocusOnAperture,
-                ));
+                // key_action_pairs.push((Modifiers::NONE, TIMING_KEY, WindowAction::FocusOnTiming));
+                // key_action_pairs.push((
+                //     Modifiers::NONE,
+                //     APERTURE_KEY,
+                //     WindowAction::FocusOnAperture,
+                // ));
                 key_action_pairs.push((Modifiers::NONE, Key::Escape, WindowAction::CloseWindow));
             }
-            ValveWindowState::TimingFocused => {
+            ValveViewState::TimingFocused => {
                 // The timing field is focused, so we can map the keys to control the timing
                 key_action_pairs.push((Modifiers::NONE, Key::Enter, WindowAction::SetTiming));
                 key_action_pairs.push((Modifiers::NONE, Key::Escape, WindowAction::LooseFocus));
             }
-            ValveWindowState::ApertureFocused => {
+            ValveViewState::ApertureFocused => {
                 // The aperture field is focused, so we can map the keys to control the aperture
                 key_action_pairs.push((Modifiers::NONE, Key::Enter, WindowAction::SetAperture));
                 key_action_pairs.push((Modifiers::NONE, Key::Escape, WindowAction::LooseFocus));
             }
-            ValveWindowState::Closed => {}
+            ValveViewState::Closed => {}
         }
         shortcut_handler.consume_if_mode_is(ShortcutMode::valve_control(), &key_action_pairs[..])
     }
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
-enum ValveWindowState {
+enum ValveViewState {
     Closed,
     Open,
     TimingFocused,
     ApertureFocused,
 }
 
-impl ValveWindowState {
+impl ValveViewState {
     #[inline]
     fn is_open(&self) -> bool {
         matches!(self, Self::Open)
-- 
GitLab