More refactoring
This commit is contained in:
parent
dced5cba03
commit
19a039f98d
3
Linux/.vscode/settings.json
vendored
3
Linux/.vscode/settings.json
vendored
@ -1,5 +1,6 @@
|
||||
{
|
||||
"nixEnvSelector.nixFile": "${workspaceFolder}/shell.nix",
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"rust-analyzer.imports.granularity.group": "item"
|
||||
"rust-analyzer.imports.granularity.group": "item",
|
||||
"rust-analyzer.check.command": "clippy"
|
||||
}
|
671
Linux/Cargo.lock
generated
671
Linux/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,22 +1,6 @@
|
||||
[package]
|
||||
name = "MassiveKnob"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.86"
|
||||
env_logger = "0.11.3"
|
||||
log = "0.4.21"
|
||||
platform-dirs = "0.3.0"
|
||||
regex = "1.10.5"
|
||||
relm4 = "0.8.1"
|
||||
relm4-icons = "0.8.3"
|
||||
rust-i18n = "3.0.1"
|
||||
serde = { version = "1.0.203", features = ["derive"] }
|
||||
serde_json = "1.0.117"
|
||||
serialport = { version = "4.4.0", default-features = false }
|
||||
tracker = "0.2.2"
|
||||
|
||||
[dependencies.min-rs]
|
||||
git = "https://github.com/MvRens/min-rs.git"
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"backend",
|
||||
"ui-gtk"
|
||||
]
|
||||
|
23
Linux/backend/Cargo.toml
Normal file
23
Linux/backend/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "massiveknob_backend"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.86"
|
||||
log = "0.4.21"
|
||||
platform-dirs = "0.3.0"
|
||||
regex = "1.10.5"
|
||||
rust-i18n = "3.0.1"
|
||||
serde = { version = "1.0.203", features = ["derive"] }
|
||||
serde_json = "1.0.117"
|
||||
serialport = { version = "4.4.0", default-features = false }
|
||||
tracker = "0.2.2"
|
||||
|
||||
[dependencies.uuid]
|
||||
version = "1.10.0"
|
||||
features = [ "v4", "fast-rng", "macro-diagnostics" ]
|
||||
|
||||
[dependencies.min-rs]
|
||||
git = "https://github.com/MvRens/min-rs.git"
|
9
Linux/backend/locales/actions/pipewire.yaml
Normal file
9
Linux/backend/locales/actions/pipewire.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
_version: 2
|
||||
|
||||
actions:
|
||||
pipewire:
|
||||
group_name:
|
||||
en: Pipewire
|
||||
set_volume:
|
||||
name:
|
||||
en: Set volume
|
6
Linux/backend/locales/devices/serial_min.yaml
Normal file
6
Linux/backend/locales/devices/serial_min.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
_version: 2
|
||||
|
||||
devices:
|
||||
serial_min:
|
||||
name:
|
||||
en: Serial device using MIN protocol
|
42
Linux/backend/src/actions/mod.rs
Normal file
42
Linux/backend/src/actions/mod.rs
Normal file
@ -0,0 +1,42 @@
|
||||
pub mod pipewire;
|
||||
|
||||
|
||||
use crate::util::unique_id::UniqueId;
|
||||
|
||||
|
||||
pub enum ActionGroup
|
||||
{
|
||||
Pipewire(pipewire::PipewireActionGroupInfo)
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub trait ActionGroupInfo
|
||||
{
|
||||
/// The unique ID of the group. This should remain stable across releases for
|
||||
/// the purpose of storing it in the user's configuration.
|
||||
fn unique_id(&self) -> UniqueId;
|
||||
|
||||
/// The name of the group for display purposes.
|
||||
fn name(&self) -> String
|
||||
{
|
||||
t!(format!("actions.{}.name", self.unique_id().as_str()).as_str()).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub trait ActionInfo
|
||||
{
|
||||
/// The unique ID of the item. This should remain stable across releases for
|
||||
/// the purpose of storing it in the user's configuration.
|
||||
fn unique_id(&self) -> UniqueId;
|
||||
|
||||
/// The unique ID of the group this action belongs to.
|
||||
fn group_id(&self) -> UniqueId;
|
||||
|
||||
/// The name of the item for display purposes.
|
||||
fn name(&self) -> String
|
||||
{
|
||||
t!(format!("actions.{}.{}.name", self.group_id().as_str(), self.unique_id().as_str()).as_str()).to_string()
|
||||
}
|
||||
}
|
23
Linux/backend/src/actions/pipewire/mod.rs
Normal file
23
Linux/backend/src/actions/pipewire/mod.rs
Normal file
@ -0,0 +1,23 @@
|
||||
use crate::util::unique_id::UniqueId;
|
||||
|
||||
use super::ActionGroupInfo;
|
||||
|
||||
pub mod set_volume;
|
||||
|
||||
|
||||
|
||||
pub enum PipewireAction
|
||||
{
|
||||
SetVolume(set_volume::PipewireSetVolumeActionInfo)
|
||||
}
|
||||
|
||||
|
||||
pub struct PipewireActionGroupInfo
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
impl ActionGroupInfo for PipewireActionGroupInfo
|
||||
{
|
||||
fn unique_id(&self) -> UniqueId { UniqueId::from("pipewire") }
|
||||
}
|
13
Linux/backend/src/actions/pipewire/set_volume.rs
Normal file
13
Linux/backend/src/actions/pipewire/set_volume.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use crate::actions::ActionInfo;
|
||||
use crate::util::unique_id::UniqueId;
|
||||
|
||||
pub struct PipewireSetVolumeActionInfo
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
impl ActionInfo for PipewireSetVolumeActionInfo
|
||||
{
|
||||
fn unique_id(&self) -> crate::util::unique_id::UniqueId { UniqueId::from("set_volume") }
|
||||
fn group_id(&self) -> crate::util::unique_id::UniqueId { UniqueId::from("pipewire") }
|
||||
}
|
@ -19,6 +19,7 @@ pub struct ConfigManager
|
||||
|
||||
impl ConfigManager
|
||||
{
|
||||
#![allow(clippy::new_without_default)]
|
||||
pub fn new() -> Self
|
||||
{
|
||||
let appdirs = AppDirs::new(Some("massiveknob"), false).unwrap();
|
18
Linux/backend/src/devices/emulator.rs
Normal file
18
Linux/backend/src/devices/emulator.rs
Normal file
@ -0,0 +1,18 @@
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::DeviceContext;
|
||||
|
||||
pub struct EmulatorDevice
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
impl EmulatorDevice
|
||||
{
|
||||
pub fn new(_context: DeviceContext, _instance_id: Uuid) -> Self
|
||||
{
|
||||
Self
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
87
Linux/backend/src/devices/mod.rs
Normal file
87
Linux/backend/src/devices/mod.rs
Normal file
@ -0,0 +1,87 @@
|
||||
pub mod emulator;
|
||||
pub mod serial_min;
|
||||
|
||||
|
||||
use emulator::EmulatorDevice;
|
||||
use serial_min::SerialMinDevice;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::util::unique_id::UniqueId;
|
||||
|
||||
// TODO use a procedural macro to build the registry based on attributes in the Device enum?
|
||||
|
||||
pub enum Device
|
||||
{
|
||||
Emulator(emulator::EmulatorDevice),
|
||||
SerialMin(serial_min::SerialMinDevice)
|
||||
}
|
||||
|
||||
|
||||
pub struct DeviceInfo
|
||||
{
|
||||
/// The unique ID of the item. This should remain stable across releases for
|
||||
/// the purpose of storing it in the user's configuration.
|
||||
pub unique_id: UniqueId,
|
||||
|
||||
pub factory: fn(DeviceContext, Uuid) -> Device
|
||||
}
|
||||
|
||||
|
||||
pub struct DeviceRegistry
|
||||
{
|
||||
info: Vec<DeviceInfo>
|
||||
}
|
||||
|
||||
|
||||
pub struct DeviceContext
|
||||
{
|
||||
// Arc<ConfigManager>
|
||||
}
|
||||
|
||||
|
||||
impl DeviceRegistry
|
||||
{
|
||||
#![allow(clippy::new_without_default)]
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self
|
||||
{
|
||||
info: vec!(
|
||||
DeviceInfo
|
||||
{
|
||||
unique_id: UniqueId::from("emulator"),
|
||||
factory: |context, instance_id| Device::Emulator(EmulatorDevice::new(context, instance_id))
|
||||
},
|
||||
|
||||
DeviceInfo
|
||||
{
|
||||
unique_id: UniqueId::from("serial_min"),
|
||||
factory: |context, instance_id| Device::SerialMin(SerialMinDevice::new(context, instance_id))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &DeviceInfo>
|
||||
{
|
||||
self.info.iter()
|
||||
}
|
||||
|
||||
|
||||
pub fn by_id(&self, id: &UniqueId) -> Option<&DeviceInfo>
|
||||
{
|
||||
self.info.iter().find(|v| &v.unique_id == id)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl DeviceInfo
|
||||
{
|
||||
/// The name of the item for display purposes.
|
||||
pub fn name(&self) -> String
|
||||
{
|
||||
t!(format!("devices.{}.name", self.unique_id.as_str()).as_str()).to_string()
|
||||
}
|
||||
}
|
19
Linux/backend/src/devices/serial_min.rs
Normal file
19
Linux/backend/src/devices/serial_min.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::DeviceContext;
|
||||
|
||||
|
||||
pub struct SerialMinDevice
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
impl SerialMinDevice
|
||||
{
|
||||
pub fn new(_context: DeviceContext, _instance_id: Uuid) -> Self
|
||||
{
|
||||
Self
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
12
Linux/backend/src/lib.rs
Normal file
12
Linux/backend/src/lib.rs
Normal file
@ -0,0 +1,12 @@
|
||||
#[macro_use]
|
||||
extern crate rust_i18n;
|
||||
|
||||
i18n!("locales");
|
||||
|
||||
|
||||
pub mod util;
|
||||
pub mod devices;
|
||||
pub mod actions;
|
||||
|
||||
pub mod config;
|
||||
pub mod orchestrator;
|
@ -129,12 +129,16 @@ impl MainWindow
|
||||
{
|
||||
if initial
|
||||
{
|
||||
self.orchestrator.with_active_device(|device_instance, cookie| { widget = device_instance.create_settings_widget(cookie) });
|
||||
if let Some(active_device) = self.orchestrator.active_device()
|
||||
{
|
||||
active_device
|
||||
}
|
||||
self.orchestrator.with_active_device(|device_instance| { widget = device_instance.create_settings_widget() });
|
||||
}
|
||||
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.orchestrator.set_active_device_id(&device_id, |device_instance| { widget = device_instance.create_settings_widget() });
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,14 @@
|
||||
use std::borrow::{Borrow, BorrowMut};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::actions;
|
||||
//use crate::actions::ActionRegistryItem;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::config::json::JsonConfigManager;
|
||||
use crate::config::{ConfigManager, ConfigName};
|
||||
use crate::devices::{self, Device};
|
||||
use crate::devices::DeviceRegistryItem;
|
||||
use crate::registry::Registry;
|
||||
use crate::config::ConfigName;
|
||||
use crate::config::ConfigManager;
|
||||
use crate::devices::DeviceContext;
|
||||
use crate::devices::DeviceInfo;
|
||||
use crate::devices::DeviceRegistry;
|
||||
use crate::devices::Device;
|
||||
use crate::util::unique_id::UniqueId;
|
||||
|
||||
|
||||
@ -19,44 +20,30 @@ pub struct Orchestrator
|
||||
config_manager: ConfigManager,
|
||||
settings_name: ConfigName,
|
||||
|
||||
device_registry: Registry<DeviceRegistryItem>,
|
||||
//action_registry: Registry<ActionRegistryItem>,
|
||||
device_registry: DeviceRegistry,
|
||||
|
||||
// current_device_instance: Option<Box<dyn Device>>
|
||||
active_device: Option<ActiveDevice>
|
||||
}
|
||||
|
||||
|
||||
impl Orchestrator
|
||||
{
|
||||
#![allow(clippy::new_without_default)]
|
||||
pub fn new() -> Self
|
||||
{
|
||||
let config_manager = ConfigManager::new();
|
||||
let settings_name = ConfigName::new("settings");
|
||||
let settings = match config_manager.read_json(&settings_name).expect("Error reading settings")
|
||||
{
|
||||
None => settings::Settings::default(),
|
||||
Some(v) => v
|
||||
};
|
||||
let settings = config_manager.read_json(&settings_name).expect("Error reading settings").unwrap_or_default();
|
||||
|
||||
|
||||
let mut device_registry = Registry::new();
|
||||
let mut action_registry = Registry::new();
|
||||
|
||||
devices::register(&mut device_registry);
|
||||
actions::register(&mut action_registry);
|
||||
|
||||
let mut instance = Self
|
||||
{
|
||||
config_manager,
|
||||
settings_name,
|
||||
|
||||
device_registry,
|
||||
//action_registry,
|
||||
|
||||
device_registry: DeviceRegistry::new(),
|
||||
//settings,
|
||||
|
||||
|
||||
active_device: None
|
||||
};
|
||||
|
||||
@ -75,7 +62,7 @@ impl Orchestrator
|
||||
}
|
||||
|
||||
|
||||
pub fn devices(&self) -> impl Iterator<Item = &DeviceRegistryItem>
|
||||
pub fn devices(&self) -> impl Iterator<Item = &DeviceInfo>
|
||||
{
|
||||
self.device_registry.iter()
|
||||
}
|
||||
@ -87,38 +74,31 @@ impl Orchestrator
|
||||
Some(active_device.id.clone())
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
|
||||
pub fn current_device(&self) -> Option<&DeviceRegistryItem>
|
||||
pub fn active_device(&self) -> Option<DeviceReference>
|
||||
{
|
||||
let Some(device_id) = self.current_device_id() else { return None };
|
||||
self.device_registry.by_id(device_id)
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
pub fn with_active_device<F>(&self, callback: F) where F: FnOnce(&dyn Device)
|
||||
{
|
||||
let Some(active_device) = self.active_device else { return };
|
||||
let Some(active_device) = &self.active_device else { return None };
|
||||
let instance = active_device.instance.clone();
|
||||
|
||||
callback(instance.as_ref().as_ref());
|
||||
|
||||
self.active_device.as_ref().map(|device| callback(&*device.instance.clone()));
|
||||
Some(instance)
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub fn set_active_device_id<F>(&mut self, id: &UniqueId, on_changed: F) where F: FnOnce(&dyn Device)
|
||||
pub fn set_active_device_id(&mut self, id: &UniqueId) -> DeviceReference
|
||||
{
|
||||
let id = Some(id.clone());
|
||||
if id == self.active_device_id() { return }
|
||||
if let Some(active_device) = &self.active_device
|
||||
{
|
||||
if *id == active_device.id
|
||||
{
|
||||
return active_device.instance.clone();
|
||||
}
|
||||
}
|
||||
|
||||
self.set_active_device(id);
|
||||
|
||||
let active_device = self.set_active_device(Some(id.clone())).unwrap_or_else(|| panic!("Invalid device ID: {}", id.as_str()));
|
||||
self.store_settings();
|
||||
|
||||
self.with_active_device(on_changed);
|
||||
active_device
|
||||
}
|
||||
|
||||
|
||||
@ -127,11 +107,7 @@ impl Orchestrator
|
||||
{
|
||||
let settings = settings::Settings
|
||||
{
|
||||
device_id: match &self.active_device
|
||||
{
|
||||
None => None,
|
||||
Some(v) => Some(v.id.clone().into())
|
||||
}
|
||||
device_id: self.active_device.as_ref().map(|v| v.id.clone().into())
|
||||
};
|
||||
|
||||
if let Err(e) = self.config_manager.write_json(&self.settings_name, &settings)
|
||||
@ -142,9 +118,9 @@ impl Orchestrator
|
||||
|
||||
|
||||
|
||||
fn set_active_device(&mut self, id: Option<UniqueId>)
|
||||
fn set_active_device(&mut self, id: Option<UniqueId>) -> Option<DeviceReference>
|
||||
{
|
||||
self.active_device = match id
|
||||
let new_device = match id
|
||||
{
|
||||
None => None,
|
||||
Some(v) =>
|
||||
@ -153,14 +129,25 @@ impl Orchestrator
|
||||
None => None,
|
||||
Some(d) =>
|
||||
{
|
||||
let context = DeviceContext
|
||||
{
|
||||
};
|
||||
|
||||
let instance_id = Uuid::new_v4();
|
||||
|
||||
Some(ActiveDevice
|
||||
{
|
||||
id: v.clone(),
|
||||
instance: Arc::new((d.factory)())
|
||||
instance: Arc::new((d.factory)(context, instance_id))
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let result = new_device.as_ref().map(|v| v.instance.clone());
|
||||
|
||||
self.active_device = new_device;
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,8 +162,11 @@ impl Drop for Orchestrator
|
||||
|
||||
|
||||
|
||||
pub type DeviceReference = Arc<Device>;
|
||||
|
||||
|
||||
struct ActiveDevice
|
||||
{
|
||||
id: UniqueId,
|
||||
instance: Arc<Box<dyn Device>>
|
||||
instance: DeviceReference
|
||||
}
|
7
Linux/backend/src/orchestrator/settings.rs
Normal file
7
Linux/backend/src/orchestrator/settings.rs
Normal file
@ -0,0 +1,7 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
pub struct Settings
|
||||
{
|
||||
pub device_id: Option<String>
|
||||
}
|
@ -68,11 +68,11 @@ impl<T: ValidatedStringPattern> From<String> for ValidatedString<T>
|
||||
}
|
||||
|
||||
|
||||
impl<T: ValidatedStringPattern> Into<String> for ValidatedString<T>
|
||||
impl<T: ValidatedStringPattern> From<ValidatedString<T>> for String
|
||||
{
|
||||
fn into(self) -> String
|
||||
fn from(val: ValidatedString<T>) -> Self
|
||||
{
|
||||
self.inner.clone()
|
||||
val.inner.clone()
|
||||
}
|
||||
}
|
||||
|
@ -1,37 +0,0 @@
|
||||
pub mod pipewire;
|
||||
|
||||
use crate::registry::Registry;
|
||||
use crate::registry::RegistryItem;
|
||||
use crate::util::unique_id::UniqueId;
|
||||
|
||||
|
||||
pub struct ActionRegistryItem
|
||||
{
|
||||
pub unique_id: UniqueId
|
||||
}
|
||||
|
||||
|
||||
pub trait Action
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
impl RegistryItem for ActionRegistryItem
|
||||
{
|
||||
fn unique_id(&self) -> UniqueId
|
||||
{
|
||||
self.unique_id.clone()
|
||||
}
|
||||
|
||||
fn name(&self) -> String
|
||||
{
|
||||
t!(format!("actions.{}.name", self.unique_id.as_str()).as_str()).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub fn register(registry: &mut Registry<ActionRegistryItem>)
|
||||
{
|
||||
pipewire::register(registry);
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
use crate::registry::Registry;
|
||||
use crate::util::unique_id::UniqueId;
|
||||
use super::ActionRegistryItem;
|
||||
|
||||
pub mod set_volume;
|
||||
|
||||
|
||||
pub fn register(registry: &mut Registry<ActionRegistryItem>)
|
||||
{
|
||||
registry.register(ActionRegistryItem
|
||||
{
|
||||
unique_id: UniqueId::from("pipewire.set_volume")
|
||||
});
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
use gtk::prelude::*;
|
||||
use relm4::prelude::*;
|
||||
|
||||
pub struct EmulatorWindow
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EmulatorWindowMessage
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
#[relm4::component(pub)]
|
||||
impl SimpleComponent for EmulatorWindow
|
||||
{
|
||||
type Init = ();
|
||||
type Input = EmulatorWindowMessage;
|
||||
type Output = ();
|
||||
|
||||
view!
|
||||
{
|
||||
gtk::Window
|
||||
{
|
||||
set_title: Some(&t!("emulatorwindow.title")),
|
||||
set_default_size: (300, 500)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn init(_data: Self::Init, root: Self::Root, _sender: ComponentSender<Self>, ) -> ComponentParts<Self>
|
||||
{
|
||||
let model = EmulatorWindow {};
|
||||
let widgets = view_output!();
|
||||
|
||||
root.set_visible(true);
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
|
||||
fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>)
|
||||
{
|
||||
match msg
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
use emulatorwindow::EmulatorWindow;
|
||||
use relm4::component::Connector;
|
||||
use relm4::Component;
|
||||
use relm4::gtk::prelude::*;
|
||||
use relm4::ComponentController;
|
||||
|
||||
use crate::registry::Registry;
|
||||
use crate::util::unique_id::UniqueId;
|
||||
use super::Device;
|
||||
use super::DeviceRegistryItem;
|
||||
|
||||
|
||||
pub mod emulatorwindow;
|
||||
|
||||
|
||||
pub struct EmulatorWindowDevice
|
||||
{
|
||||
window: Connector<EmulatorWindow>
|
||||
}
|
||||
|
||||
|
||||
impl EmulatorWindowDevice
|
||||
{
|
||||
fn new() -> Self
|
||||
{
|
||||
let builder = EmulatorWindow::builder();
|
||||
let window = builder.launch({});
|
||||
|
||||
EmulatorWindowDevice
|
||||
{
|
||||
window
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Device for EmulatorWindowDevice
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
impl Drop for EmulatorWindowDevice
|
||||
{
|
||||
fn drop(&mut self)
|
||||
{
|
||||
self.window.widget().close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn register(registry: &mut Registry<DeviceRegistryItem>)
|
||||
{
|
||||
registry.register(DeviceRegistryItem {
|
||||
unique_id: UniqueId::from("emulator"),
|
||||
factory: || Box::new(EmulatorWindowDevice::new()),
|
||||
settings_widget_factory: || None
|
||||
});
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
pub mod emulator;
|
||||
pub mod serial_min;
|
||||
|
||||
|
||||
use crate::registry::Registry;
|
||||
use crate::registry::RegistryItem;
|
||||
use crate::ui::EmbeddedWidgetConnector;
|
||||
use crate::util::unique_id::UniqueId;
|
||||
|
||||
|
||||
pub struct DeviceRegistryItem
|
||||
{
|
||||
pub unique_id: UniqueId,
|
||||
pub factory: fn() -> Box<dyn Device>,
|
||||
pub settings_widget_factory: fn() -> Option<Box<dyn EmbeddedWidgetConnector>>
|
||||
}
|
||||
|
||||
|
||||
pub trait Device
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
impl RegistryItem for DeviceRegistryItem
|
||||
{
|
||||
fn unique_id(&self) -> UniqueId
|
||||
{
|
||||
self.unique_id.clone()
|
||||
}
|
||||
|
||||
fn name(&self) -> String
|
||||
{
|
||||
t!(format!("devices.{}.name", self.unique_id.as_str()).as_str()).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub fn register(registry: &mut Registry<DeviceRegistryItem>)
|
||||
{
|
||||
emulator::register(registry);
|
||||
serial_min::register(registry);
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
use relm4::Component;
|
||||
use settingswidget::SerialMinSettingsInit;
|
||||
use settingswidget::SerialMinSettingsWidget;
|
||||
|
||||
use crate::registry::Registry;
|
||||
use crate::util::unique_id::UniqueId;
|
||||
use super::Device;
|
||||
use super::DeviceRegistryItem;
|
||||
|
||||
|
||||
pub mod settingswidget;
|
||||
|
||||
|
||||
pub struct SerialMinDevice
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
impl Device for SerialMinDevice
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
pub fn register(registry: &mut Registry<DeviceRegistryItem>)
|
||||
{
|
||||
registry.register(DeviceRegistryItem {
|
||||
unique_id: UniqueId::from("serial_min"),
|
||||
factory: || Box::new(SerialMinDevice {}),
|
||||
settings_widget_factory: ||
|
||||
{
|
||||
let builder = SerialMinSettingsWidget::builder();
|
||||
Some(Box::new(builder.launch(SerialMinSettingsInit
|
||||
{
|
||||
})))
|
||||
}
|
||||
});
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
use env_logger::Env;
|
||||
use relm4::prelude::*;
|
||||
|
||||
#[macro_use]
|
||||
extern crate rust_i18n;
|
||||
|
||||
i18n!("locales");
|
||||
|
||||
|
||||
pub mod util;
|
||||
pub mod ui;
|
||||
pub mod registry;
|
||||
pub mod devices;
|
||||
pub mod actions;
|
||||
|
||||
pub mod config;
|
||||
pub mod orchestrator;
|
||||
pub mod mainwindow;
|
||||
|
||||
fn main()
|
||||
{
|
||||
env_logger::Builder::from_env(Env::default().default_filter_or("info"))
|
||||
// .format_timestamp(None)
|
||||
.init();
|
||||
|
||||
relm4_icons::initialize_icons();
|
||||
|
||||
let app = RelmApp::new("com.github.mvrens.massiveknob");
|
||||
app.run::<mainwindow::MainWindow>(());
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Settings
|
||||
{
|
||||
pub device_id: Option<String>
|
||||
}
|
||||
|
||||
|
||||
impl Default for Settings
|
||||
{
|
||||
fn default() -> Self
|
||||
{
|
||||
Self
|
||||
{
|
||||
device_id: None
|
||||
}
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use log;
|
||||
|
||||
use crate::util::unique_id::UniqueId;
|
||||
|
||||
|
||||
pub trait RegistryItem
|
||||
{
|
||||
/// The unique ID of the item. This should remain stable across releases for
|
||||
/// the purpose of storing it in the user's configuration.
|
||||
fn unique_id(&self) -> UniqueId;
|
||||
|
||||
/// The name of the item for display purposes.
|
||||
fn name(&self) -> String;
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub struct Registry<T> where T: RegistryItem
|
||||
{
|
||||
items: HashMap<String, T>
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl<T> Registry<T> where T: RegistryItem
|
||||
{
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self
|
||||
{
|
||||
items: HashMap::new()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn register(&mut self, device: T)
|
||||
{
|
||||
let device_id = device.unique_id();
|
||||
|
||||
log::debug!("Registered device: [{}] {}", device_id.as_str(), device.name());
|
||||
self.items.insert(String::from(device_id.as_str()), device);
|
||||
}
|
||||
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &T>
|
||||
{
|
||||
self.items.values()
|
||||
}
|
||||
|
||||
|
||||
pub fn by_id(&self, id: &UniqueId) -> Option<&T>
|
||||
{
|
||||
self.items.get(id.as_str())
|
||||
}
|
||||
}
|
16
Linux/ui-gtk/Cargo.toml
Normal file
16
Linux/ui-gtk/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "massiveknob_ui_gtk"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
env_logger = "0.11.3"
|
||||
gtk = { version = "0.9.0", package = "gtk4", features = ["v4_14"] }
|
||||
log = "0.4.21"
|
||||
rust-i18n = "3.0.1"
|
||||
serialport = { version = "4.4.0", default-features = false }
|
||||
tracker = "0.2.2"
|
||||
|
||||
[dependencies.massiveknob_backend]
|
||||
path = "../backend"
|
4
Linux/ui-gtk/build.rs
Normal file
4
Linux/ui-gtk/build.rs
Normal file
@ -0,0 +1,4 @@
|
||||
fn main()
|
||||
{
|
||||
println!("cargo:rerun-if-changed=locales");
|
||||
}
|
2
Linux/ui-gtk/icons.toml
Normal file
2
Linux/ui-gtk/icons.toml
Normal file
@ -0,0 +1,2 @@
|
||||
base_resource_path = "/com/github/mvrens/massiveknob/"
|
||||
icons = []
|
6
Linux/ui-gtk/locales/devices/emulator.yaml
Normal file
6
Linux/ui-gtk/locales/devices/emulator.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
_version: 2
|
||||
|
||||
devices:
|
||||
emulator:
|
||||
name:
|
||||
en: Emulator
|
101
Linux/ui-gtk/src/devices/emulator_settings_ui.rs
Normal file
101
Linux/ui-gtk/src/devices/emulator_settings_ui.rs
Normal file
@ -0,0 +1,101 @@
|
||||
use massiveknob_backend::orchestrator::DeviceReference;
|
||||
|
||||
use crate::ui::uicomponent::UiComponent;
|
||||
use crate::ui::uicomponent::UiComponentState;
|
||||
|
||||
|
||||
pub struct EmulatorSettingsUiInit
|
||||
{
|
||||
pub device: DeviceReference
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub struct EmulatorSettingsUi
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
pub struct EmulatorSettingsUiWidgets
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
impl UiComponent for EmulatorSettingsUi
|
||||
{
|
||||
type Root = gtk::Box;
|
||||
type Widgets = EmulatorSettingsUiWidgets;
|
||||
type Init = EmulatorSettingsUiInit;
|
||||
type State = Self;
|
||||
|
||||
|
||||
fn build_root(_init: &Self::Init) -> Self::Root
|
||||
{
|
||||
gtk::Box::builder()
|
||||
.hexpand(true)
|
||||
.orientation(gtk::Orientation::Vertical)
|
||||
.spacing(8)
|
||||
.build()
|
||||
}
|
||||
|
||||
fn build_widgets(_root: &Self::Root, _init: &Self::Init) -> Self::Widgets
|
||||
{
|
||||
Self::Widgets
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
fn init(_root: &Self::Root, _state: &std::rc::Rc<std::cell::RefCell<Self::State>>)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl UiComponentState<EmulatorSettingsUi> for EmulatorSettingsUi
|
||||
{
|
||||
fn new(_init: (), _widgets: EmulatorSettingsUiWidgets) -> Self
|
||||
{
|
||||
Self
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
#[relm4::component(pub)]
|
||||
impl SimpleComponent for EmulatorWindow
|
||||
{
|
||||
type Init = ();
|
||||
type Input = EmulatorWindowMessage;
|
||||
type Output = ();
|
||||
|
||||
view!
|
||||
{
|
||||
gtk::Window
|
||||
{
|
||||
set_title: Some(&t!("emulatorwindow.title")),
|
||||
set_default_size: (300, 500)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn init(_data: Self::Init, root: Self::Root, _sender: ComponentSender<Self>, ) -> ComponentParts<Self>
|
||||
{
|
||||
let model = EmulatorWindow {};
|
||||
let widgets = view_output!();
|
||||
|
||||
root.set_visible(true);
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
|
||||
fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>)
|
||||
{
|
||||
match msg
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
30
Linux/ui-gtk/src/devices/mod.rs
Normal file
30
Linux/ui-gtk/src/devices/mod.rs
Normal file
@ -0,0 +1,30 @@
|
||||
use emulator_settings_ui::EmulatorSettingsUi;
|
||||
use emulator_settings_ui::EmulatorSettingsUiInit;
|
||||
use massiveknob_backend::devices::Device;
|
||||
use massiveknob_backend::orchestrator::DeviceReference;
|
||||
use serial_min_settings_ui::SerialMinSettingsUi;
|
||||
use serial_min_settings_ui::SerialMinSettingsUiInit;
|
||||
|
||||
use crate::ui::uicomponent::UiComponent;
|
||||
use crate::ui::uicomponent::UiComponentConnectorWidget;
|
||||
|
||||
pub mod emulator_settings_ui;
|
||||
pub mod serial_min_settings_ui;
|
||||
|
||||
|
||||
pub struct DeviceSettingsUiBuilder
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
impl DeviceSettingsUiBuilder
|
||||
{
|
||||
pub fn build(device: DeviceReference) -> Box<dyn UiComponentConnectorWidget>
|
||||
{
|
||||
match device.as_ref()
|
||||
{
|
||||
Device::Emulator(_) => Box::new(EmulatorSettingsUi::builder().build(EmulatorSettingsUiInit { device })),
|
||||
Device::SerialMin(_) => Box::new(SerialMinSettingsUi::builder().build(SerialMinSettingsUiInit { device }))
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +1,13 @@
|
||||
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;
|
||||
use gtk::prelude::*;
|
||||
use gtk::StringList;
|
||||
use massiveknob_backend::orchestrator::DeviceReference;
|
||||
use crate::ui::uicomponent::UiComponent;
|
||||
use crate::ui::uicomponent::UiComponentState;
|
||||
|
||||
|
||||
#[tracker::track]
|
||||
pub struct SerialMinSettingsWidget
|
||||
pub struct SerialMinSettingsUi
|
||||
{
|
||||
#[do_not_track]
|
||||
ports: Vec<String>,
|
||||
@ -19,28 +17,130 @@ pub struct SerialMinSettingsWidget
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SerialMinSettingsWidgetMessage
|
||||
pub struct SerialMinSettingsUiInit
|
||||
{
|
||||
PortChanged(usize),
|
||||
CustomPortChanged(String)
|
||||
pub device: DeviceReference
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
pub struct SerialMinSettingsUiWidgets
|
||||
{
|
||||
custom_port_input: gtk::Entry
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl UiComponent for SerialMinSettingsUi
|
||||
{
|
||||
type Root = gtk::Box;
|
||||
type Widgets = SerialMinSettingsUiWidgets;
|
||||
type Init = SerialMinSettingsUiInit;
|
||||
type State = Self;
|
||||
|
||||
|
||||
fn build_root(_init: &Self::Init) -> Self::Root
|
||||
{
|
||||
gtk::Box::builder()
|
||||
.hexpand(true)
|
||||
.orientation(gtk::Orientation::Vertical)
|
||||
.spacing(8)
|
||||
.build()
|
||||
}
|
||||
|
||||
fn build_widgets(root: &Self::Root, init: &Self::Init) -> Self::Widgets
|
||||
{
|
||||
let port_label = gtk::Label::builder()
|
||||
.label(t!("serial_min.settings.port.label"))
|
||||
.halign(gtk::Align::Start)
|
||||
.build();
|
||||
|
||||
root.append(&port_label);
|
||||
|
||||
|
||||
|
||||
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)
|
||||
.build();
|
||||
|
||||
root.append(&port_select);
|
||||
|
||||
|
||||
let port_select_cloned = port_select.clone();
|
||||
port_select.connect_selected_notify(clone!(
|
||||
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())));
|
||||
}
|
||||
));
|
||||
*/
|
||||
|
||||
Self::Widgets
|
||||
{
|
||||
custom_port_input
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn init(root: &Self::Root, state: &std::rc::Rc<std::cell::RefCell<Self::State>>)
|
||||
{
|
||||
|
||||
.selected(ports.len().try_into().unwrap_or(gtk::ffi::GTK_INVALID_LIST_POSITION))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl UiComponentState<SerialMinSettingsUi> for SerialMinSettingsUi
|
||||
{
|
||||
fn new(_init: <SerialMinSettingsUi as UiComponent>::Init, _widgets: <SerialMinSettingsUi as UiComponent>::Widgets) -> Self
|
||||
{
|
||||
let ports_list = serialport::available_ports().unwrap_or_default();
|
||||
let ports: Vec<String> = ports_list.iter().map(|p| p.port_name.clone()).collect();
|
||||
|
||||
|
||||
// TODO read settings
|
||||
|
||||
Self
|
||||
{
|
||||
ports,
|
||||
custom_port: String::default(),
|
||||
custom_port_visible: false,
|
||||
tracker: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
impl SimpleComponent for SerialMinSettingsWidget
|
||||
{
|
||||
type Init = SerialMinSettingsInit;
|
||||
@ -62,81 +162,7 @@ impl SimpleComponent for SerialMinSettingsWidget
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
|
||||
@ -180,4 +206,4 @@ impl EmbeddedWidgetConnector for Connector<SerialMinSettingsWidget>
|
||||
{
|
||||
self.widget().as_ref()
|
||||
}
|
||||
}
|
||||
} */
|
53
Linux/ui-gtk/src/main.rs
Normal file
53
Linux/ui-gtk/src/main.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use env_logger::Env;
|
||||
use gtk::glib;
|
||||
use gtk::prelude::*;
|
||||
use mainwindow::MainWindow;
|
||||
use mainwindow::MainWindowInit;
|
||||
use massiveknob_backend::orchestrator::Orchestrator;
|
||||
use ui::uicomponent::UiComponent;
|
||||
|
||||
|
||||
const APP_ID: &str = "com.github.mvrens.massiveknob";
|
||||
|
||||
|
||||
#[macro_use]
|
||||
extern crate rust_i18n;
|
||||
|
||||
i18n!("locales");
|
||||
|
||||
|
||||
pub mod ui;
|
||||
pub mod devices;
|
||||
|
||||
pub mod mainwindow;
|
||||
|
||||
fn main() -> glib::ExitCode
|
||||
{
|
||||
env_logger::Builder::from_env(Env::default().default_filter_or("info"))
|
||||
// .format_timestamp(None)
|
||||
.init();
|
||||
|
||||
|
||||
let app = gtk::Application::builder()
|
||||
.application_id(APP_ID)
|
||||
.build();
|
||||
|
||||
app.connect_activate(activate);
|
||||
app.run()
|
||||
}
|
||||
|
||||
|
||||
fn activate(app: >k::Application)
|
||||
{
|
||||
let orchestrator = Arc::new(Mutex::new(Orchestrator::new()));
|
||||
let mainwindow = MainWindow::builder().build(MainWindowInit
|
||||
{
|
||||
app: app.clone(),
|
||||
orchestrator: orchestrator.clone()
|
||||
});
|
||||
|
||||
mainwindow.root.present();
|
||||
}
|
257
Linux/ui-gtk/src/mainwindow.rs
Normal file
257
Linux/ui-gtk/src/mainwindow.rs
Normal file
@ -0,0 +1,257 @@
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use gtk::glib;
|
||||
use gtk::glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use massiveknob_backend::orchestrator::Orchestrator;
|
||||
use massiveknob_backend::util::unique_id::UniqueId;
|
||||
|
||||
use crate::devices::DeviceSettingsUiBuilder;
|
||||
use crate::ui::uicomponent::UiComponent;
|
||||
use crate::ui::uicomponent::UiComponentState;
|
||||
|
||||
|
||||
|
||||
pub struct MainWindowInit
|
||||
{
|
||||
pub app: gtk::Application,
|
||||
pub orchestrator: Arc<Mutex<Orchestrator>>
|
||||
}
|
||||
|
||||
|
||||
pub struct MainWindow
|
||||
{
|
||||
orchestrator: Arc<Mutex<Orchestrator>>,
|
||||
devices_sorted: Vec<SortedDevice>,
|
||||
widgets: MainWindowWidgets,
|
||||
|
||||
device_settings_widget: Option<gtk::Widget>
|
||||
}
|
||||
|
||||
|
||||
pub struct MainWindowWidgets
|
||||
{
|
||||
device: MainWindowDeviceWidgets
|
||||
}
|
||||
|
||||
|
||||
pub struct MainWindowDeviceWidgets
|
||||
{
|
||||
devices_dropdown: gtk::DropDown,
|
||||
settings_container: gtk::Box
|
||||
}
|
||||
|
||||
|
||||
impl UiComponent for MainWindow
|
||||
{
|
||||
type Root = gtk::ApplicationWindow;
|
||||
type Widgets = MainWindowWidgets;
|
||||
type Init = MainWindowInit;
|
||||
type State = Self;
|
||||
|
||||
|
||||
fn build_root(init: &Self::Init) -> Self::Root
|
||||
{
|
||||
gtk::ApplicationWindow::builder()
|
||||
.application(&init.app)
|
||||
.title(t!("mainwindow.title"))
|
||||
.default_width(500)
|
||||
.default_height(500)
|
||||
.build()
|
||||
}
|
||||
|
||||
fn build_widgets(root: &Self::Root, _init: &Self::Init) -> Self::Widgets
|
||||
{
|
||||
let tabs = gtk::Notebook::builder().build();
|
||||
root.set_child(Some(&tabs));
|
||||
|
||||
Self::Widgets
|
||||
{
|
||||
device: Self::build_device_tab(&tabs)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn init(_root: &Self::Root, state: &Rc<RefCell<Self::State>>)
|
||||
{
|
||||
{
|
||||
let state_borrowed = state.borrow();
|
||||
let devices_dropdown = state_borrowed.widgets.device.devices_dropdown.clone();
|
||||
let orchestrator = state_borrowed.orchestrator.lock().unwrap();
|
||||
|
||||
let active_device_id = orchestrator.active_device_id();
|
||||
let mut active_device_index = gtk::ffi::GTK_INVALID_LIST_POSITION;
|
||||
let devices_dropdown_list = gtk::StringList::default();
|
||||
|
||||
for (index, device) in state_borrowed.devices_sorted.iter().enumerate()
|
||||
{
|
||||
devices_dropdown_list.append(device.name.as_str());
|
||||
|
||||
if let Some(device_id) = &active_device_id
|
||||
{
|
||||
if *device_id == device.unique_id
|
||||
{
|
||||
active_device_index = index as u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
devices_dropdown.set_model(Some(&devices_dropdown_list));
|
||||
devices_dropdown.set_selected(active_device_index);
|
||||
|
||||
devices_dropdown.connect_selected_notify(clone!(
|
||||
#[weak]
|
||||
state,
|
||||
|
||||
move |_|
|
||||
{
|
||||
let mut state = state.borrow_mut();
|
||||
state.update_active_device(true);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
let mut state = state.borrow_mut();
|
||||
state.update_active_device(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl UiComponentState<MainWindow> for MainWindow
|
||||
{
|
||||
fn new(init: MainWindowInit, widgets: MainWindowWidgets) -> Self
|
||||
{
|
||||
let mut devices_sorted: Vec<SortedDevice>;
|
||||
{
|
||||
let orchestrator = init.orchestrator.lock().unwrap();
|
||||
|
||||
devices_sorted = orchestrator.devices()
|
||||
.map(|device| SortedDevice
|
||||
{
|
||||
unique_id: device.unique_id.clone(),
|
||||
name: device.name()
|
||||
})
|
||||
.collect();
|
||||
|
||||
devices_sorted.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase()));
|
||||
}
|
||||
|
||||
|
||||
Self
|
||||
{
|
||||
orchestrator: init.orchestrator.clone(),
|
||||
devices_sorted,
|
||||
widgets,
|
||||
|
||||
device_settings_widget: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl MainWindow
|
||||
{
|
||||
fn build_device_tab(tabs: >k::Notebook) -> MainWindowDeviceWidgets
|
||||
{
|
||||
//let sender = self.sender;
|
||||
let tab = Self::build_box_tab(tabs, "mainwindow.tab.device");
|
||||
|
||||
let label = gtk::Label::builder()
|
||||
.label(t!("mainwindow.deviceType.label"))
|
||||
.halign(gtk::Align::Start)
|
||||
.build();
|
||||
|
||||
tab.append(&label);
|
||||
|
||||
|
||||
let devices_dropdown = gtk::DropDown::builder().build();
|
||||
tab.append(&devices_dropdown);
|
||||
|
||||
|
||||
let settings_container = gtk::Box::builder().build();
|
||||
tab.append(&settings_container);
|
||||
|
||||
|
||||
MainWindowDeviceWidgets
|
||||
{
|
||||
devices_dropdown,
|
||||
settings_container
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn build_box_tab(notebook: >k::Notebook, title_key: &str) -> gtk::Box
|
||||
{
|
||||
let tab = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Vertical)
|
||||
.spacing(8)
|
||||
.margin_start(8)
|
||||
.margin_end(8)
|
||||
.margin_top(8)
|
||||
.margin_bottom(8)
|
||||
.build();
|
||||
|
||||
let tab_label = gtk::Label::builder()
|
||||
.label(t!(title_key))
|
||||
.build();
|
||||
|
||||
notebook.append_page(&tab, Some(&tab_label));
|
||||
|
||||
tab
|
||||
}
|
||||
|
||||
|
||||
fn update_active_device(&mut self, set_active: bool)
|
||||
{
|
||||
log::info!("update_active_device");
|
||||
|
||||
let active_index = self.widgets.device.devices_dropdown.selected();
|
||||
if active_index == gtk::ffi::GTK_INVALID_LIST_POSITION { return };
|
||||
|
||||
let Ok(active_index_usize) = usize::try_from(active_index) else { return };
|
||||
let selected_device = &self.devices_sorted[active_index_usize];
|
||||
let device;
|
||||
|
||||
{
|
||||
let mut orchestrator = self.orchestrator.lock().unwrap();
|
||||
|
||||
if set_active
|
||||
{
|
||||
device = Some(orchestrator.set_active_device_id(&selected_device.unique_id));
|
||||
}
|
||||
else
|
||||
{
|
||||
device = orchestrator.active_device();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(prev_widget) = &self.device_settings_widget
|
||||
{
|
||||
self.widgets.device.settings_container.remove(prev_widget);
|
||||
}
|
||||
|
||||
if let Some(device) = device
|
||||
{
|
||||
let widget = DeviceSettingsUiBuilder::build(device.clone());
|
||||
|
||||
self.widgets.device.settings_container.append(&widget);
|
||||
self.device_settings_widget = Some(widget);
|
||||
}
|
||||
else
|
||||
{
|
||||
self.device_settings_widget = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
struct SortedDevice
|
||||
{
|
||||
unique_id: UniqueId,
|
||||
name: String
|
||||
}
|
8
Linux/ui-gtk/src/ui/mod.rs
Normal file
8
Linux/ui-gtk/src/ui/mod.rs
Normal file
@ -0,0 +1,8 @@
|
||||
pub mod uicomponent;
|
||||
|
||||
|
||||
#[deprecated]
|
||||
pub trait EmbeddedWidgetConnector
|
||||
{
|
||||
fn root(&self) -> gtk::Widget;
|
||||
}
|
104
Linux/ui-gtk/src/ui/uicomponent.rs
Normal file
104
Linux/ui-gtk/src/ui/uicomponent.rs
Normal file
@ -0,0 +1,104 @@
|
||||
// Much credit to Relm4 for the inspiration. I initially used it, but felt
|
||||
// like I wasn't using much of the abstractions it provided. But some parts
|
||||
// are still very useful and I recommend giving it a try for your project!
|
||||
|
||||
use gtk::prelude::*;
|
||||
use std::cell::RefCell;
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
||||
pub trait UiComponent : Sized
|
||||
{
|
||||
type Root : IsA<gtk::Widget>;
|
||||
type Widgets;
|
||||
type Init;
|
||||
type State : UiComponentState<Self>;
|
||||
|
||||
|
||||
fn builder() -> UiComponentBuilder<Self>
|
||||
{
|
||||
UiComponentBuilder::<Self>::default()
|
||||
}
|
||||
|
||||
|
||||
fn build_root(init: &Self::Init) -> Self::Root;
|
||||
fn build_widgets(root: &Self::Root, init: &Self::Init) -> Self::Widgets;
|
||||
|
||||
fn init(root: &Self::Root, state: &Rc<RefCell<Self::State>>);
|
||||
}
|
||||
|
||||
|
||||
pub trait UiComponentState<C: UiComponent>
|
||||
{
|
||||
fn new(init: C::Init, widgets: C::Widgets) -> Self;
|
||||
}
|
||||
|
||||
|
||||
pub struct UiComponentConnector<C: UiComponent>
|
||||
{
|
||||
pub root: C::Root,
|
||||
pub state: Rc<RefCell<C::State>>
|
||||
}
|
||||
|
||||
|
||||
pub trait UiComponentConnectorWidget
|
||||
{
|
||||
fn root(&self) -> gtk::Widget;
|
||||
}
|
||||
|
||||
|
||||
pub struct UiComponentBuilder<C: UiComponent>
|
||||
{
|
||||
marker: PhantomData<C>
|
||||
}
|
||||
|
||||
|
||||
impl<C: UiComponent> Default for UiComponentBuilder<C>
|
||||
{
|
||||
fn default() -> Self
|
||||
{
|
||||
Self
|
||||
{
|
||||
marker: PhantomData::<C>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<C: UiComponent> UiComponentBuilder<C>
|
||||
{
|
||||
pub fn build(&self, init: C::Init) -> UiComponentConnector<C>
|
||||
{
|
||||
let root = C::build_root(&init);
|
||||
let widgets = C::build_widgets(&root, &init);
|
||||
let state = Rc::new(RefCell::new(C::State::new(init, widgets)));
|
||||
|
||||
C::init(&root, &state);
|
||||
|
||||
|
||||
UiComponentConnector::<C>
|
||||
{
|
||||
root,
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<C: UiComponent> From<UiComponentConnector<C>> for gtk::Widget
|
||||
{
|
||||
fn from(val: UiComponentConnector<C>) -> Self
|
||||
{
|
||||
val.root.into()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<C: UiComponent> UiComponentConnectorWidget for UiComponentConnector<C>
|
||||
{
|
||||
fn root(&self) -> gtk::Widget
|
||||
{
|
||||
self.root.clone().into()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user