1
0
mirror of synced 2024-11-05 09:49:16 +00:00

Refactoring

Trying to find nice coding patterns as I'm learning Rust
This commit is contained in:
Mark van Renswoude 2024-08-12 09:20:27 +02:00
parent 50eb38aae1
commit dced5cba03
18 changed files with 560 additions and 192 deletions

View File

@ -1,4 +1,5 @@
{ {
"nixEnvSelector.nixFile": "${workspaceFolder}/shell.nix", "nixEnvSelector.nixFile": "${workspaceFolder}/shell.nix",
"files.trimTrailingWhitespace": true "files.trimTrailingWhitespace": true,
"rust-analyzer.imports.granularity.group": "item"
} }

85
Linux/Cargo.lock generated
View File

@ -17,6 +17,8 @@ dependencies = [
"rust-i18n", "rust-i18n",
"serde", "serde",
"serde_json", "serde_json",
"serialport",
"tracker",
] ]
[[package]] [[package]]
@ -211,6 +213,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
[[package]]
name = "core-foundation-sys"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
[[package]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.4.2" version = "1.4.2"
@ -803,6 +811,16 @@ dependencies = [
"hashbrown 0.14.5", "hashbrown 0.14.5",
] ]
[[package]]
name = "io-kit-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b"
dependencies = [
"core-foundation-sys",
"mach2",
]
[[package]] [[package]]
name = "is_terminal_polyfill" name = "is_terminal_polyfill"
version = "1.70.0" version = "1.70.0"
@ -868,6 +886,15 @@ version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "mach2"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.2" version = "2.7.2"
@ -909,6 +936,17 @@ dependencies = [
"getrandom", "getrandom",
] ]
[[package]]
name = "nix"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
dependencies = [
"bitflags 1.3.2",
"cfg-if",
"libc",
]
[[package]] [[package]]
name = "normpath" name = "normpath"
version = "1.2.0" version = "1.2.0"
@ -1271,6 +1309,24 @@ dependencies = [
"yaml-rust", "yaml-rust",
] ]
[[package]]
name = "serialport"
version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de7c4f0cce25b9b3518eea99618112f9ee4549f974480c8f43d3c06f03c131a0"
dependencies = [
"bitflags 2.5.0",
"cfg-if",
"core-foundation-sys",
"io-kit-sys",
"mach2",
"nix",
"regex",
"scopeguard",
"unescaper",
"winapi",
]
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.9" version = "0.4.9"
@ -1480,6 +1536,26 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "tracker"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce5c98457ff700aaeefcd4a4a492096e78a2af1dd8523c66e94a3adb0fdbd415"
dependencies = [
"tracker-macros",
]
[[package]]
name = "tracker-macros"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc19eb2373ccf3d1999967c26c3d44534ff71ae5d8b9dacf78f4b13132229e48"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
]
[[package]] [[package]]
name = "triomphe" name = "triomphe"
version = "0.1.12" version = "0.1.12"
@ -1491,6 +1567,15 @@ dependencies = [
"stable_deref_trait", "stable_deref_trait",
] ]
[[package]]
name = "unescaper"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c878a167baa8afd137494101a688ef8c67125089ff2249284bd2b5f9bfedb815"
dependencies = [
"thiserror",
]
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.12" version = "1.0.12"

View File

@ -15,6 +15,8 @@ relm4-icons = "0.8.3"
rust-i18n = "3.0.1" rust-i18n = "3.0.1"
serde = { version = "1.0.203", features = ["derive"] } serde = { version = "1.0.203", features = ["derive"] }
serde_json = "1.0.117" serde_json = "1.0.117"
serialport = { version = "4.4.0", default-features = false }
tracker = "0.2.2"
[dependencies.min-rs] [dependencies.min-rs]
git = "https://github.com/MvRens/min-rs.git" git = "https://github.com/MvRens/min-rs.git"

View File

@ -3,4 +3,14 @@ _version: 2
devices: devices:
serial_min: serial_min:
name: name:
en: Serial device using MIN protocol en: Serial device using MIN protocol
serial_min:
settings:
port:
label:
en: Serial port
custom:
en: Custom...
custom_port_placeholder:
en: e.g. /dev/ttyACM0

View File

@ -1,10 +1,11 @@
{ pkgs ? import <nixpkgs> {} }: { pkgs ? import <nixpkgs> {} }:
pkgs.mkShell { pkgs.mkShell {
nativeBuildInputs = with pkgs; [ nativeBuildInputs = with pkgs; [
pkg-config pkg-config
gtk4 gtk4
graphene graphene
gdk-pixbuf gdk-pixbuf
libudev-zero
]; ];
# For a reason I'm yet to find out, my VSCode terminal sets GDK_BACKEND to x11. # For a reason I'm yet to find out, my VSCode terminal sets GDK_BACKEND to x11.

View File

@ -9,6 +9,6 @@ pub fn register(registry: &mut Registry<ActionRegistryItem>)
{ {
registry.register(ActionRegistryItem registry.register(ActionRegistryItem
{ {
unique_id: UniqueId::new("pipewire.set_volume") unique_id: UniqueId::from("pipewire.set_volume")
}); });
} }

View File

@ -2,12 +2,12 @@ use gtk::prelude::*;
use relm4::prelude::*; use relm4::prelude::*;
pub struct EmulatorWindow pub struct EmulatorWindow
{ {
} }
#[derive(Debug)] #[derive(Debug)]
pub enum Msg pub enum EmulatorWindowMessage
{ {
} }
@ -16,20 +16,20 @@ pub enum Msg
impl SimpleComponent for EmulatorWindow impl SimpleComponent for EmulatorWindow
{ {
type Init = (); type Init = ();
type Input = Msg; type Input = EmulatorWindowMessage;
type Output = (); type Output = ();
view! view!
{ {
gtk::Window gtk::Window
{ {
set_title: Some(&t!("emulatorwindow.title")), set_title: Some(&t!("emulatorwindow.title")),
set_default_size: (300, 500) set_default_size: (300, 500)
} }
} }
fn init(_data: Self::Init, root: Self::Root, _sender: ComponentSender<Self>, ) -> ComponentParts<Self> fn init(_data: Self::Init, root: Self::Root, _sender: ComponentSender<Self>, ) -> ComponentParts<Self>
{ {
let model = EmulatorWindow {}; let model = EmulatorWindow {};
let widgets = view_output!(); let widgets = view_output!();
@ -39,10 +39,10 @@ impl SimpleComponent for EmulatorWindow
} }
fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>)
{ {
match msg match msg
{ {
} }
} }
} }

View File

@ -1,11 +1,13 @@
use emulatorwindow::EmulatorWindow; use emulatorwindow::EmulatorWindow;
use relm4::{component::Connector, ComponentController}; use relm4::component::Connector;
use relm4::Component; use relm4::Component;
use relm4::gtk::prelude::*; use relm4::gtk::prelude::*;
use relm4::ComponentController;
use crate::registry::Registry; use crate::registry::Registry;
use crate::util::unique_id::UniqueId; use crate::util::unique_id::UniqueId;
use super::{Device, DeviceRegistryItem}; use super::Device;
use super::DeviceRegistryItem;
pub mod emulatorwindow; pub mod emulatorwindow;
@ -13,7 +15,7 @@ pub mod emulatorwindow;
pub struct EmulatorWindowDevice pub struct EmulatorWindowDevice
{ {
window: Option<Connector<EmulatorWindow>> window: Connector<EmulatorWindow>
} }
@ -21,9 +23,12 @@ impl EmulatorWindowDevice
{ {
fn new() -> Self fn new() -> Self
{ {
let builder = EmulatorWindow::builder();
let window = builder.launch({});
EmulatorWindowDevice EmulatorWindowDevice
{ {
window: None window
} }
} }
} }
@ -31,20 +36,14 @@ impl EmulatorWindowDevice
impl Device for EmulatorWindowDevice impl Device for EmulatorWindowDevice
{ {
fn activate(&mut self) }
impl Drop for EmulatorWindowDevice
{
fn drop(&mut self)
{ {
if self.window.is_some() { return } self.window.widget().close();
let builder = EmulatorWindow::builder();
self.window = Some(builder.launch({}));
}
fn deactivate(&mut self)
{
let Some(window) = self.window.take() else { return };
window.widget().close();
} }
} }
@ -52,7 +51,8 @@ impl Device for EmulatorWindowDevice
pub fn register(registry: &mut Registry<DeviceRegistryItem>) pub fn register(registry: &mut Registry<DeviceRegistryItem>)
{ {
registry.register(DeviceRegistryItem { registry.register(DeviceRegistryItem {
unique_id: UniqueId::new("emulator"), unique_id: UniqueId::from("emulator"),
factory: || Box::new(EmulatorWindowDevice::new()) factory: || Box::new(EmulatorWindowDevice::new()),
settings_widget_factory: || None
}); });
} }

View File

@ -4,20 +4,20 @@ pub mod serial_min;
use crate::registry::Registry; use crate::registry::Registry;
use crate::registry::RegistryItem; use crate::registry::RegistryItem;
use crate::ui::EmbeddedWidgetConnector;
use crate::util::unique_id::UniqueId; use crate::util::unique_id::UniqueId;
pub struct DeviceRegistryItem pub struct DeviceRegistryItem
{ {
pub unique_id: UniqueId, pub unique_id: UniqueId,
pub factory: fn() -> Box<dyn Device> pub factory: fn() -> Box<dyn Device>,
pub settings_widget_factory: fn() -> Option<Box<dyn EmbeddedWidgetConnector>>
} }
pub trait Device pub trait Device
{ {
fn activate(&mut self);
fn deactivate(&mut self);
} }

View File

@ -1,6 +1,14 @@
use relm4::Component;
use settingswidget::SerialMinSettingsInit;
use settingswidget::SerialMinSettingsWidget;
use crate::registry::Registry; use crate::registry::Registry;
use crate::util::unique_id::UniqueId; use crate::util::unique_id::UniqueId;
use super::{Device, DeviceRegistryItem}; use super::Device;
use super::DeviceRegistryItem;
pub mod settingswidget;
pub struct SerialMinDevice pub struct SerialMinDevice
@ -11,23 +19,20 @@ pub struct SerialMinDevice
impl Device for SerialMinDevice impl Device for SerialMinDevice
{ {
fn activate(&mut self)
{
//todo!()
}
fn deactivate(&mut self)
{
//todo!()
}
} }
pub fn register(registry: &mut Registry<DeviceRegistryItem>) pub fn register(registry: &mut Registry<DeviceRegistryItem>)
{ {
registry.register(DeviceRegistryItem { registry.register(DeviceRegistryItem {
unique_id: UniqueId::new("serial_min"), unique_id: UniqueId::from("serial_min"),
factory: || Box::new(SerialMinDevice {}) factory: || Box::new(SerialMinDevice {}),
settings_widget_factory: ||
{
let builder = SerialMinSettingsWidget::builder();
Some(Box::new(builder.launch(SerialMinSettingsInit
{
})))
}
}); });
} }

View File

@ -0,0 +1,183 @@
use gtk::StringList;
use gtk::glib::clone;
use relm4::component::Connector;
use relm4::prelude::*;
use relm4::gtk::prelude::*;
use serialport::SerialPortInfo;
use crate::ui::EmbeddedWidgetConnector;
#[tracker::track]
pub struct SerialMinSettingsWidget
{
#[do_not_track]
ports: Vec<String>,
custom_port: String,
custom_port_visible: bool
}
#[derive(Debug)]
pub enum SerialMinSettingsWidgetMessage
{
PortChanged(usize),
CustomPortChanged(String)
}
pub struct SerialMinSettingsInit
{
// this needs a good design - we need to be able to modify the device instance, but
// we can't pass the reference or an Rc to the device due to the design of create_settings_widget.
// Either seperate the UI from the Device, or pass a middle man (sender/receiver style perhaps?)
}
pub struct SerialMinSettingsWidgets
{
custom_port_input: gtk::Entry
}
impl SimpleComponent for SerialMinSettingsWidget
{
type Init = SerialMinSettingsInit;
type Input = SerialMinSettingsWidgetMessage;
type Output = ();
type Root = gtk::Box;
type Widgets = SerialMinSettingsWidgets;
fn init_root() -> Self::Root
{
gtk::Box::builder()
.hexpand(true)
.orientation(gtk::Orientation::Vertical)
.spacing(8)
.build()
}
fn init(_data: Self::Init, root: Self::Root, sender: ComponentSender<Self>, ) -> ComponentParts<Self>
{
let port_label = gtk::Label::builder()
.label(t!("serial_min.settings.port.label"))
.halign(gtk::Align::Start)
.build();
root.append(&port_label);
let ports_list = serialport::available_ports().unwrap_or(Vec::<SerialPortInfo>::new());
let ports: Vec<String> = ports_list.iter().map(|p| p.port_name.clone()).collect();
let port_model_vec: Vec<&str> = ports.iter().map(|p| p.as_str()).collect();
let port_model = StringList::new(&port_model_vec);
port_model.append(t!("serial_min.settings.port.custom").as_ref());
let port_select = gtk::DropDown::builder()
.model(&port_model)
.selected(ports.len().try_into().unwrap_or(gtk::ffi::GTK_INVALID_LIST_POSITION))
.build();
root.append(&port_select);
let port_select_cloned = port_select.clone();
port_select.connect_selected_notify(clone!(
@strong sender => move |_|
{
let active_index = port_select_cloned.selected();
if active_index == gtk::ffi::GTK_INVALID_LIST_POSITION { return };
if let Ok(active_index_usize) = usize::try_from(active_index)
{
sender.input(SerialMinSettingsWidgetMessage::PortChanged(active_index_usize));
}
}));
let custom_port_input = gtk::Entry::builder()
.hexpand(true)
.placeholder_text(t!("serial_min.settings.custom_port_placeholder"))
.build();
root.append(&custom_port_input);
let custom_port_input_cloned = custom_port_input.clone();
custom_port_input.connect_changed(clone!(
@strong sender => move |_|
{
sender.input(SerialMinSettingsWidgetMessage::CustomPortChanged(String::from(custom_port_input_cloned.text().as_str())));
}
));
let model = SerialMinSettingsWidget
{
ports,
custom_port: String::new(),
custom_port_visible: true,
tracker: 0
};
// TODO load settings
let widgets = SerialMinSettingsWidgets
{
custom_port_input
};
ComponentParts { model, widgets }
}
fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>)
{
match msg
{
SerialMinSettingsWidgetMessage::PortChanged(index) =>
{
self.set_custom_port_visible(index >= self.ports.len());
},
SerialMinSettingsWidgetMessage::CustomPortChanged(value) =>
{
self.set_custom_port(value);
}
}
}
fn update_view(&self, widgets: &mut Self::Widgets, _sender: ComponentSender<Self>)
{
if self.changed_custom_port_visible()
{
log::info!("Visible: {}", self.custom_port_visible);
widgets.custom_port_input.set_visible(self.custom_port_visible);
}
if self.changed_custom_port()
{
// should this sync two-way or not?
//widgets
}
}
}
impl EmbeddedWidgetConnector for Connector<SerialMinSettingsWidget>
{
fn root(&self) -> &relm4::gtk::Widget
{
self.widget().as_ref()
}
}

View File

@ -1,8 +1,4 @@
use std::cell::RefCell;
use std::rc::Rc;
use env_logger::Env; use env_logger::Env;
use mainwindow::MainWindowInit;
use orchestrator::Orchestrator;
use relm4::prelude::*; use relm4::prelude::*;
#[macro_use] #[macro_use]
@ -12,6 +8,7 @@ i18n!("locales");
pub mod util; pub mod util;
pub mod ui;
pub mod registry; pub mod registry;
pub mod devices; pub mod devices;
pub mod actions; pub mod actions;
@ -28,13 +25,6 @@ fn main()
relm4_icons::initialize_icons(); relm4_icons::initialize_icons();
let orchestrator = Rc::new(RefCell::new(Orchestrator::new()));
let app = RelmApp::new("com.github.mvrens.massiveknob"); let app = RelmApp::new("com.github.mvrens.massiveknob");
app.run::<mainwindow::MainWindow>(MainWindowInit app.run::<mainwindow::MainWindow>(());
{
orchestrator: Rc::clone(&orchestrator)
});
orchestrator.borrow_mut().finalize();
} }

View File

@ -1,59 +1,49 @@
use std::cell::RefCell;
use std::rc::Rc;
use gtk::glib::clone; use gtk::glib::clone;
use gtk::prelude::*; use gtk::prelude::*;
use relm4::prelude::*; use relm4::prelude::*;
use crate::orchestrator::Orchestrator; use crate::orchestrator::Orchestrator;
use crate::registry::RegistryItem; use crate::registry::RegistryItem;
use crate::ui::EmbeddedWidgetConnector;
use crate::util::unique_id::UniqueId; use crate::util::unique_id::UniqueId;
#[tracker::track]
pub struct MainWindow pub struct MainWindow
{ {
orchestrator: Rc<RefCell<Orchestrator>>, #[do_not_track]
devices_sorted: Vec<SortedDevice> orchestrator: Orchestrator,
}
#[do_not_track]
devices_sorted: Vec<SortedDevice>,
pub struct MainWindowInit #[no_eq]
{ device_settings_widget: Option<Box<dyn EmbeddedWidgetConnector>>
pub orchestrator: Rc<RefCell<Orchestrator>>
}
impl std::fmt::Debug for MainWindowInit
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
{
f.debug_struct("MainWindowInit")
// Skip orchestrator
.finish()
}
} }
#[derive(Debug)] #[derive(Debug)]
pub enum MainWindowMsg pub enum MainWindowMsg
{ {
DeviceInitial(usize),
DeviceChanged(usize) DeviceChanged(usize)
} }
pub struct MainWindowWidgets pub struct MainWindowWidgets
{ {
_device: MainWindowDeviceWidgets device: MainWindowDeviceWidgets
} }
pub struct MainWindowDeviceWidgets pub struct MainWindowDeviceWidgets
{ {
_devices_combobox: gtk::ComboBoxText settings_container: gtk::Box
} }
impl SimpleComponent for MainWindow impl SimpleComponent for MainWindow
{ {
type Init = MainWindowInit; type Init = ();
type Input = MainWindowMsg; type Input = MainWindowMsg;
type Output = (); type Output = ();
type Root = gtk::Window; type Root = gtk::Window;
@ -71,20 +61,14 @@ impl SimpleComponent for MainWindow
} }
fn init(data: Self::Init, window: Self::Root, sender: ComponentSender<Self>) -> ComponentParts<Self> fn init(_data: Self::Init, window: Self::Root, sender: ComponentSender<Self>) -> ComponentParts<Self>
{ {
{ let orchestrator = Orchestrator::new();
let mut init_orchestrator = data.orchestrator.borrow_mut();
init_orchestrator.initialize();
}
let orchestrator = data.orchestrator.borrow();
let mut devices_sorted: Vec<SortedDevice> = orchestrator.devices() let mut devices_sorted: Vec<SortedDevice> = orchestrator.devices()
.map(|device| SortedDevice .map(|device| SortedDevice
{ {
unique_id: device.unique_id(), unique_id: device.unique_id.clone(),
name: device.name() name: device.name()
}) })
.collect(); .collect();
@ -94,8 +78,12 @@ impl SimpleComponent for MainWindow
let model = MainWindow let model = MainWindow
{ {
orchestrator: data.orchestrator.clone(), orchestrator,
devices_sorted devices_sorted,
device_settings_widget: None,
tracker: 0
}; };
let widgets = MainWindowBuilder::new(&window, &model, &sender).build(); let widgets = MainWindowBuilder::new(&window, &model, &sender).build();
@ -108,18 +96,53 @@ impl SimpleComponent for MainWindow
{ {
match msg match msg
{ {
MainWindowMsg::DeviceChanged(index) => MainWindowMsg::DeviceInitial(index) => self.apply_device_settings_widget(index, true),
{ MainWindowMsg::DeviceChanged(index) => self.apply_device_settings_widget(index, false)
let mut orchestrator = self.orchestrator.borrow_mut(); }
let device = &self.devices_sorted[index]; }
orchestrator.set_current_device_id(device.unique_id.clone());
fn update_view(&self, widgets: &mut Self::Widgets, _sender: ComponentSender<Self>)
{
if self.changed(MainWindow::device_settings_widget())
{
let current_child = widgets.device.settings_container.last_child();
if let Some(current_child) = &current_child
{
widgets.device.settings_container.remove(current_child);
}
if let Some(new_child) = &self.device_settings_widget
{
widgets.device.settings_container.append(new_child.as_ref().root());
} }
} }
} }
} }
impl MainWindow
{
fn apply_device_settings_widget(&mut self, index: usize, initial: bool)
{
let mut widget = None;
{
if initial
{
self.orchestrator.with_active_device(|device_instance, cookie| { widget = device_instance.create_settings_widget(cookie) });
}
else
{
let device_id = self.devices_sorted[index].unique_id.clone();
self.orchestrator.set_active_device_id(&device_id, |device_instance, cookie| { widget = device_instance.create_settings_widget(cookie) });
}
}
self.set_device_settings_widget(widget)
}
}
struct MainWindowBuilder<'a> struct MainWindowBuilder<'a>
{ {
window: &'a gtk::Window, window: &'a gtk::Window,
@ -148,7 +171,7 @@ impl<'a> MainWindowBuilder<'a>
MainWindowWidgets MainWindowWidgets
{ {
_device: self.init_device_tab(&tabs) device: self.init_device_tab(&tabs)
//Self::new_box_tab(&tabs, "mainwindow.tab.analoginputs"); //Self::new_box_tab(&tabs, "mainwindow.tab.analoginputs");
//Self::new_box_tab(&tabs, "mainwindow.tab.digitalinputs"); //Self::new_box_tab(&tabs, "mainwindow.tab.digitalinputs");
//Self::add_box_tab(&tabs, "mainwindow.tab.analogoutputs"); //Self::add_box_tab(&tabs, "mainwindow.tab.analogoutputs");
@ -171,11 +194,27 @@ impl<'a> MainWindowBuilder<'a>
let devices_combobox = gtk::ComboBoxText::builder() let devices_combobox = gtk::ComboBoxText::builder().build();
.build(); tab.append(&devices_combobox);
let active_device_id = self.model.orchestrator.active_device_id();
for (index, device) in self.model.devices_sorted.iter().enumerate()
{
devices_combobox.append_text(device.name.as_str());
if let Some(device_id) = &active_device_id
{
if *device_id == device.unique_id
{
devices_combobox.set_active(Some(index as u32));
sender.input(MainWindowMsg::DeviceInitial(index));
}
}
}
let devices_combobox_cloned = devices_combobox.clone(); let devices_combobox_cloned = devices_combobox.clone();
devices_combobox.connect_changed(clone!( devices_combobox.connect_changed(clone!(
@strong sender => move |_| @strong sender => move |_|
{ {
@ -188,29 +227,14 @@ impl<'a> MainWindowBuilder<'a>
} }
})); }));
tab.append(&devices_combobox);
let settings_container = gtk::Box::builder().build();
let current_device_id = self.model.orchestrator.borrow().current_device_id(); tab.append(&settings_container);
for (index, device) in self.model.devices_sorted.iter().enumerate()
{
devices_combobox.append_text(device.name.as_str());
if let Some(device_id) = current_device_id.clone()
{
if device_id == device.unique_id
{
devices_combobox.set_active(Some(index as u32));
}
}
}
MainWindowDeviceWidgets MainWindowDeviceWidgets
{ {
_devices_combobox: devices_combobox settings_container
} }
} }
@ -237,6 +261,7 @@ impl<'a> MainWindowBuilder<'a>
} }
#[derive(Clone)]
struct SortedDevice struct SortedDevice
{ {
unique_id: UniqueId, unique_id: UniqueId,

View File

@ -1,5 +1,8 @@
use std::borrow::{Borrow, BorrowMut};
use std::sync::Arc;
use crate::actions; use crate::actions;
use crate::actions::ActionRegistryItem; //use crate::actions::ActionRegistryItem;
use crate::config::json::JsonConfigManager; use crate::config::json::JsonConfigManager;
use crate::config::{ConfigManager, ConfigName}; use crate::config::{ConfigManager, ConfigName};
use crate::devices::{self, Device}; use crate::devices::{self, Device};
@ -14,15 +17,13 @@ mod settings;
pub struct Orchestrator pub struct Orchestrator
{ {
config_manager: ConfigManager, config_manager: ConfigManager,
settings_name: ConfigName,
device_registry: Registry<DeviceRegistryItem>, device_registry: Registry<DeviceRegistryItem>,
action_registry: Registry<ActionRegistryItem>, //action_registry: Registry<ActionRegistryItem>,
settings_name: ConfigName, // current_device_instance: Option<Box<dyn Device>>
settings: settings::Settings, active_device: Option<ActiveDevice>
current_device_instance: Option<Box<dyn Device>>
} }
@ -31,117 +32,151 @@ impl Orchestrator
pub fn new() -> Self pub fn new() -> Self
{ {
let config_manager = ConfigManager::new(); let config_manager = ConfigManager::new();
let settings_name = ConfigName::new("settings"); let settings_name = ConfigName::new("settings");
let settings = match config_manager.read_json(&settings_name).expect("Error reading settings") let settings = match config_manager.read_json(&settings_name).expect("Error reading settings")
{ {
None => settings::Settings::new(), None => settings::Settings::default(),
Some(v) => v Some(v) => v
}; };
let mut device_registry = Registry::new(); let mut device_registry = Registry::new();
let mut action_registry = Registry::new(); let mut action_registry = Registry::new();
devices::register(&mut device_registry); devices::register(&mut device_registry);
actions::register(&mut action_registry); actions::register(&mut action_registry);
Self let mut instance = Self
{ {
config_manager, config_manager,
settings_name,
device_registry, device_registry,
action_registry, //action_registry,
settings_name, //settings,
settings,
current_device_instance: None active_device: None
};
instance.initialize(settings);
instance
}
fn initialize(&mut self, settings: settings::Settings)
{
if let Some(device_id) = settings.device_id
{
self.set_active_device(Some(UniqueId::from(device_id)));
} }
} }
pub fn initialize(&mut self)
{
self.set_current_device(self.current_device_id());
}
pub fn finalize(&mut self)
{
self.set_current_device(None);
}
pub fn devices(&self) -> impl Iterator<Item = &DeviceRegistryItem> pub fn devices(&self) -> impl Iterator<Item = &DeviceRegistryItem>
{ {
self.device_registry.iter() self.device_registry.iter()
} }
pub fn current_device_id(&self) -> Option<UniqueId> pub fn active_device_id(&self) -> Option<UniqueId>
{ {
let Some(device_id) = &self.settings.device_id else { return None }; let Some(active_device) = &self.active_device else { return None };
Some(UniqueId::new(device_id.as_str())) Some(active_device.id.clone())
} }
/*
pub fn current_device(&self) -> Option<&DeviceRegistryItem> pub fn current_device(&self) -> Option<&DeviceRegistryItem>
{ {
let Some(device_id) = self.current_device_id() else { return None }; let Some(device_id) = self.current_device_id() else { return None };
self.device_registry.by_id(device_id) self.device_registry.by_id(device_id)
} }
*/
pub fn set_current_device_id(&mut self, id: UniqueId) pub fn with_active_device<F>(&self, callback: F) where F: FnOnce(&dyn Device)
{ {
let new_id = Some(String::from(id.as_str())); let Some(active_device) = self.active_device else { return };
if new_id == self.settings.device_id { return; } let instance = active_device.instance.clone();
self.settings.device_id = new_id; callback(instance.as_ref().as_ref());
self.active_device.as_ref().map(|device| callback(&*device.instance.clone()));
}
pub fn set_active_device_id<F>(&mut self, id: &UniqueId, on_changed: F) where F: FnOnce(&dyn Device)
{
let id = Some(id.clone());
if id == self.active_device_id() { return }
self.set_active_device(id);
self.store_settings(); self.store_settings();
self.set_current_device(Some(id)); self.with_active_device(on_changed);
} }
fn store_settings(&self) fn store_settings(&self)
{ {
if let Err(e) = self.config_manager.write_json(&self.settings_name, &self.settings) let settings = settings::Settings
{
device_id: match &self.active_device
{
None => None,
Some(v) => Some(v.id.clone().into())
}
};
if let Err(e) = self.config_manager.write_json(&self.settings_name, &settings)
{ {
log::error!("Error writing settings: {e}"); log::error!("Error writing settings: {e}");
} }
} }
fn set_current_device(&mut self, id: Option<UniqueId>)
fn set_active_device(&mut self, id: Option<UniqueId>)
{ {
let prev_device_instance; self.active_device = match id
let new_device = match id
{ {
None => None, None => None,
Some(v) => self.device_registry.by_id(v) Some(v) =>
match self.device_registry.by_id(&v)
{
None => None,
Some(d) =>
{
Some(ActiveDevice
{
id: v.clone(),
instance: Arc::new((d.factory)())
})
}
}
}; };
// Replace the device instance
if let Some(new_device) = new_device
{
let mut new_device_instance = (new_device.factory)();
new_device_instance.activate();
prev_device_instance = self.current_device_instance.replace(new_device_instance);
}
else
{
prev_device_instance = self.current_device_instance.take();
}
// Deactivate the previous instance
if let Some(mut prev_device_instance) = prev_device_instance
{
prev_device_instance.deactivate();
}
} }
}
impl Drop for Orchestrator
{
fn drop(&mut self)
{
self.set_active_device(None);
}
}
struct ActiveDevice
{
id: UniqueId,
instance: Arc<Box<dyn Device>>
} }

View File

@ -7,11 +7,11 @@ pub struct Settings
} }
impl Settings impl Default for Settings
{ {
pub fn new() -> Self fn default() -> Self
{ {
Self Self
{ {
device_id: None device_id: None
} }

View File

@ -49,7 +49,7 @@ impl<T> Registry<T> where T: RegistryItem
} }
pub fn by_id(&self, id: UniqueId) -> Option<&T> pub fn by_id(&self, id: &UniqueId) -> Option<&T>
{ {
self.items.get(id.as_str()) self.items.get(id.as_str())
} }

4
Linux/src/ui/mod.rs Normal file
View File

@ -0,0 +1,4 @@
pub trait EmbeddedWidgetConnector
{
fn root(&self) -> &relm4::gtk::Widget;
}

View File

@ -22,10 +22,10 @@ impl<T: ValidatedStringPattern> ValidatedString<T>
let pattern = Regex::new(T::pattern()).unwrap(); let pattern = Regex::new(T::pattern()).unwrap();
assert!(pattern.is_match(value), "Value '{value}' has invalid characters"); assert!(pattern.is_match(value), "Value '{value}' has invalid characters");
Self Self
{ {
inner: value.to_string(), inner: value.to_string(),
_phantom: PhantomData _phantom: PhantomData
} }
} }
@ -39,14 +39,41 @@ impl<T: ValidatedStringPattern> ValidatedString<T>
impl<T: ValidatedStringPattern> Clone for ValidatedString<T> impl<T: ValidatedStringPattern> Clone for ValidatedString<T>
{ {
fn clone(&self) -> Self fn clone(&self) -> Self
{ {
Self Self
{ {
inner: self.inner.clone(), inner: self.inner.clone(),
_phantom: PhantomData _phantom: PhantomData
} }
} }
}
impl<T: ValidatedStringPattern> From<&str> for ValidatedString<T>
{
fn from(value: &str) -> Self
{
Self::new(value)
}
}
impl<T: ValidatedStringPattern> From<String> for ValidatedString<T>
{
fn from(value: String) -> Self
{
Self::new(value.as_str())
}
}
impl<T: ValidatedStringPattern> Into<String> for ValidatedString<T>
{
fn into(self) -> String
{
self.inner.clone()
}
} }