feat!: migrated ui toolkit to relm4/gtk

This commit is contained in:
Gabriele Musco 2023-06-14 13:38:22 +02:00
parent 0106168146
commit 24b28bd760
No known key found for this signature in database
GPG key ID: 1068D795C80E51DE
13 changed files with 1419 additions and 2471 deletions

2751
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -7,14 +7,16 @@ edition = "2021"
[dependencies]
expect-dialog = "1.0.0"
iced = { version = "0.9.0", features = [
"smol"
] }
iced_aw = { version = "0.5.2", default-features = false, features = [
"tab_bar", "tabs", "quad", "modal", "card"
] }
nix = "0.26.2"
relm4 = { version = "0.6.0", features = [
"libadwaita"
] }
relm4-components = "0.6.0"
relm4-icons = { version = "0.6.0", features = [
"menu", "loupe"
] }
serde = { version = "1.0.163", features = [
"derive"
] }
serde_json = "1.0.96"
tracker = "0.2.1"

View file

@ -2,6 +2,7 @@ use crate::{
constants::CMD_NAME,
file_utils::{get_config_dir, get_writer},
};
use expect_dialog::ExpectDialog;
use serde::{Deserialize, Serialize};
use std::{fs::File, io::BufReader};
@ -49,8 +50,8 @@ fn save_config_to_path(config: &Config, path_s: &String) -> Result<(), serde_jso
serde_json::to_writer_pretty(writer, config)
}
pub fn save_config(config: &Config) -> Result<(), serde_json::Error> {
save_config_to_path(config, &config_file_path())
pub fn save_config(config: &Config) {
save_config_to_path(config, &config_file_path()).expect_dialog("Failed to save config");
}
#[cfg(test)]

View file

@ -1,3 +1,10 @@
pub const APP_NAME: &str = "Rex2";
pub const APP_ID: &str = "org.gabmus.rex2";
pub const CMD_NAME: &str = "rex2";
pub const VERSION: &str = "0.1";
pub const REPO_URL: &str = "https://gitlab.com/gabmus/rex2";
pub const SINGLE_DEVELOPER: &str = "The Rex2 Team";
pub fn get_developers() -> Vec<String> {
vec!["Gabriele Musco <gabmus@disroot.org>".to_string()]
}

View file

@ -1,6 +1,6 @@
use expect_dialog::ExpectDialog;
use iced::{Application, Settings};
use ui::main_win::MainWin;
use constants::APP_ID;
use relm4::RelmApp;
use ui::app::App;
pub mod builders;
pub mod config;
@ -15,5 +15,7 @@ pub mod runner;
pub mod ui;
fn main() {
MainWin::run(Settings::default()).expect_dialog("Application encountered an error");
let app = RelmApp::new(APP_ID);
relm4_icons::initialize_icons();
app.run::<App>(())
}

View file

@ -148,16 +148,25 @@ impl Runner {
}
}
pub fn get_output(&mut self) -> String {
fn receive_output(&mut self) {
loop {
match self.receiver.try_recv() {
Ok(data) => self.output.push(data),
Err(_) => break,
};
}
}
pub fn get_output(&mut self) -> String {
self.receive_output();
self.output.concat()
}
pub fn get_rows(&mut self) -> Vec<String> {
self.receive_output();
self.output.clone()
}
fn save_log(path_s: String, log: &Vec<String>) -> Result<(), std::io::Error> {
let mut writer = get_writer(&path_s);
let log_s = log.concat();

42
src/ui/about_dialog.rs Normal file
View file

@ -0,0 +1,42 @@
use relm4::gtk::traits::GtkWindowExt;
use relm4::prelude::*;
use relm4::{ComponentParts, SimpleComponent};
use crate::constants::{APP_NAME, REPO_URL, SINGLE_DEVELOPER, VERSION, get_developers};
pub struct AboutDialog {}
impl SimpleComponent for AboutDialog {
type Init = ();
type Input = ();
type Output = ();
type Root = adw::AboutWindow;
type Widgets = adw::AboutWindow;
fn init_root() -> Self::Root {
adw::AboutWindow::builder()
.application_name(APP_NAME)
.license_type(gtk::License::Agpl30)
.version(VERSION)
.website(REPO_URL)
.developer_name(SINGLE_DEVELOPER)
.developers(get_developers())
.modal(true)
.hide_on_close(true)
.build()
}
fn init(
init: Self::Init,
root: &Self::Root,
sender: relm4::ComponentSender<Self>,
) -> relm4::ComponentParts<Self> {
let model = AboutDialog {};
let widgets = root.clone();
ComponentParts { model, widgets }
}
fn update_view(&self, dialog: &mut Self::Widgets, _sender: ComponentSender<Self>) {
dialog.present();
}
}

299
src/ui/app.rs Normal file
View file

@ -0,0 +1,299 @@
use std::time::Duration;
use crate::builders::build_monado::get_build_monado_runner;
use crate::config::{get_config, save_config, Config};
use crate::constants::APP_NAME;
use crate::profile::Profile;
use crate::profiles::valve_index::valve_index_profile;
use crate::runner::{Runner, RunnerStatus};
use crate::ui::build_window::BuildWindowMsg;
use crate::ui::debug_view::DebugViewInit;
use crate::ui::main_view::{MainView, MainViewInit, MainViewOutMsg};
use expect_dialog::ExpectDialog;
use gtk::prelude::*;
use relm4::actions::{ActionGroupName, RelmAction, RelmActionGroup};
use relm4::gtk::glib;
use relm4::{new_action_group, new_stateful_action, new_stateless_action, prelude::*};
use relm4::{ComponentParts, ComponentSender, SimpleComponent};
use super::about_dialog::AboutDialog;
use super::build_window::BuildWindow;
use super::debug_view::{DebugView, DebugViewMsg};
use super::main_view::MainViewMsg;
#[tracker::track]
pub struct App {
enable_debug_view: bool,
#[tracker::do_not_track]
main_view: Controller<MainView>,
#[tracker::do_not_track]
debug_view: Controller<DebugView>,
#[tracker::do_not_track]
about_dialog: Controller<AboutDialog>,
#[tracker::do_not_track]
build_window: Controller<BuildWindow>,
#[tracker::do_not_track]
config: Config,
#[tracker::do_not_track]
monado_runner: Option<Runner>,
#[tracker::do_not_track]
build_runner: Option<Runner>,
#[tracker::do_not_track]
profiles: Vec<Profile>,
}
#[derive(Debug)]
pub enum Msg {
UpdateView,
BuildProfile,
EnableDebugViewChanged(bool),
DoStartStopMonado,
ProfileSelected(String),
}
impl App {
fn get_profile_by_name(&self, name: &String) -> Option<&Profile> {
for profile in &self.profiles {
if &profile.name == name {
return Some(profile);
}
}
return None;
}
pub fn get_selected_profile(&self) -> Option<&Profile> {
self.get_profile_by_name(&self.config.selected_profile_name)
}
pub fn start_monado(&mut self) {
let mut runner = Runner::monado_runner_from_profile(
self.get_selected_profile()
.expect_dialog("Could not find selected profile")
.clone(),
);
runner.start();
self.monado_runner = Some(runner);
}
}
#[relm4::component(pub)]
impl SimpleComponent for App {
type Init = ();
type Input = Msg;
type Output = ();
view! {
#[root]
adw::ApplicationWindow {
set_title: Some(APP_NAME),
set_default_size: (400, 300),
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_hexpand: true,
set_vexpand: true,
gtk::Box {
set_orientation: gtk::Orientation::Horizontal,
set_hexpand: true,
set_vexpand: true,
append: model.main_view.widget(),
append: sep = &gtk::Separator {
set_orientation: gtk::Orientation::Vertical,
#[track = "model.changed(App::enable_debug_view())"]
set_visible: model.enable_debug_view,
},
append: model.debug_view.widget(),
// append: model.debug_view.widget(),
}
}
}
}
fn shutdown(&mut self, widgets: &mut Self::Widgets, output: relm4::Sender<Self::Output>) {
match &mut self.monado_runner {
None => {}
Some(runner) => {
runner.terminate();
}
}
}
fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
self.reset();
match message {
Msg::UpdateView => {
match &mut self.monado_runner {
None => {}
Some(runner) => match runner.status() {
RunnerStatus::Running => {
self.debug_view
.sender()
.emit(DebugViewMsg::LogUpdated(runner.get_rows()));
}
_ => {}
},
};
match &mut self.build_runner {
None => {}
Some(runner) => match runner.status() {
RunnerStatus::Running => {
self.build_window.sender().emit(BuildWindowMsg::UpdateContent(runner.get_output()));
}
RunnerStatus::Stopped => {
self.build_window.sender().emit(BuildWindowMsg::UpdateCanClose(true));
}
}
};
}
Msg::EnableDebugViewChanged(val) => {
self.set_enable_debug_view(val);
self.config.debug_view_enabled = val;
save_config(&self.config);
self.debug_view
.sender()
.emit(DebugViewMsg::EnableDebugViewChanged(val));
self.main_view
.sender()
.emit(MainViewMsg::EnableDebugViewChanged(val));
}
Msg::DoStartStopMonado => match &mut self.monado_runner {
None => {
self.debug_view
.sender()
.emit(DebugViewMsg::LogUpdated(vec![]));
self.start_monado();
self.main_view
.sender()
.emit(MainViewMsg::MonadoActiveChanged(true));
}
Some(runner) => match runner.status() {
RunnerStatus::Running => {
runner.terminate();
self.main_view
.sender()
.emit(MainViewMsg::MonadoActiveChanged(false));
}
RunnerStatus::Stopped => {
self.debug_view
.sender()
.emit(DebugViewMsg::LogUpdated(vec![]));
self.start_monado();
self.main_view
.sender()
.emit(MainViewMsg::MonadoActiveChanged(true));
}
},
},
Msg::BuildProfile => {
let profile = self
.get_selected_profile()
.expect_dialog("Could not find selected profile");
let mut runner = get_build_monado_runner(profile.clone());
runner.start();
self.build_window.sender().emit(BuildWindowMsg::UpdateTitle("Building Monado".into()));
self.build_window.sender().emit(BuildWindowMsg::UpdateCanClose(false));
self.build_runner = Some(runner);
},
Msg::ProfileSelected(prof_name) => {
self.config.selected_profile_name = prof_name;
save_config(&self.config);
}
}
}
fn init(
_params: Self::Init,
root: &Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
let config = get_config();
let profiles = vec![valve_index_profile()];
let model = App {
main_view: MainView::builder()
.launch(MainViewInit {
config: config.clone(),
})
.forward(sender.input_sender(), |message| match message {
MainViewOutMsg::UpdateView => Msg::UpdateView,
MainViewOutMsg::EnableDebugViewChanged(val) => Msg::EnableDebugViewChanged(val),
MainViewOutMsg::DoStartStopMonado => Msg::DoStartStopMonado,
MainViewOutMsg::ProfileSelected(name) => Msg::ProfileSelected(name),
}),
debug_view: DebugView::builder()
.launch(DebugViewInit {
enable_debug_view: config.debug_view_enabled,
})
.forward(sender.input_sender(), |message| match message {
_ => Msg::UpdateView,
}),
about_dialog: AboutDialog::builder()
.transient_for(root)
.launch(())
.detach(),
build_window: BuildWindow::builder()
.transient_for(root)
.launch(())
.detach(),
enable_debug_view: config.debug_view_enabled,
config,
tracker: 0,
profiles,
monado_runner: None,
build_runner: None,
};
let widgets = view_output!();
let mut actions = RelmActionGroup::<AppActionGroup>::new();
let buildprofile_action = {
let buildprofile_sender = model.build_window.sender().clone();
let this_sender = sender.clone();
RelmAction::<BuildProfileAction>::new_stateless(move |_| {
buildprofile_sender.send(BuildWindowMsg::Present).unwrap();
this_sender.input_sender().emit(Msg::BuildProfile);
})
};
let about_action = {
let abd_sender = model.about_dialog.sender().clone();
RelmAction::<AboutAction>::new_stateless(move |_| {
abd_sender.send(()).unwrap();
})
};
let debug_view_toggle_action: RelmAction<DebugViewToggleAction> = {
let debugtoggle_sender = sender.clone();
RelmAction::<DebugViewToggleAction>::new_stateful(
&model.enable_debug_view,
move |_, state| {
let s = *state;
*state = !s;
debugtoggle_sender.input(Msg::EnableDebugViewChanged(*state));
},
)
};
actions.add_action(about_action);
actions.add_action(buildprofile_action);
actions.add_action(debug_view_toggle_action);
root.insert_action_group(AppActionGroup::NAME, Some(&actions.into_action_group()));
model.main_view.sender().emit(MainViewMsg::UpdateProfileNames(model.profiles.iter().map(|p| p.clone().name).collect()));
let timer_sender = sender.clone();
glib::timeout_add_local(Duration::from_millis(1000), move || {
timer_sender.input(Msg::UpdateView);
return glib::Continue(true);
});
ComponentParts { model, widgets }
}
}
new_action_group!(pub AppActionGroup, "win");
new_stateless_action!(pub AboutAction, AppActionGroup, "about");
new_stateless_action!(pub BuildProfileAction, AppActionGroup, "buildprofile");
new_stateful_action!(pub DebugViewToggleAction, AppActionGroup, "debugviewtoggle", (), bool);

130
src/ui/build_window.rs Normal file
View file

@ -0,0 +1,130 @@
use gtk::prelude::*;
use relm4::prelude::*;
#[tracker::track]
pub struct BuildWindow {
title: String,
content: String,
can_close: bool,
#[tracker::do_not_track]
pub textbuf: gtk::TextBuffer,
#[tracker::do_not_track]
pub win: Option<adw::Window>,
#[tracker::do_not_track]
pub scrolled_win: Option<gtk::ScrolledWindow>,
}
#[derive(Debug)]
pub enum BuildWindowMsg {
Present,
UpdateTitle(String),
UpdateContent(String),
UpdateCanClose(bool),
}
#[relm4::component(pub)]
impl SimpleComponent for BuildWindow {
type Init = ();
type Input = BuildWindowMsg;
type Output = ();
view! {
#[name(win)]
adw::Window {
set_modal: true,
set_default_size: (520, 400),
gtk::Box {
set_vexpand: true,
set_hexpand: true,
set_orientation: gtk::Orientation::Vertical,
gtk::WindowHandle {
set_vexpand: false,
set_hexpand: true,
adw::HeaderBar {
set_show_end_title_buttons: false,
set_show_start_title_buttons: false,
add_css_class: "flat",
#[wrap(Some)]
set_title_widget: title_label = &gtk::Label {
#[track = "model.changed(BuildWindow::title())"]
set_label: &model.title,
add_css_class: "title",
},
}
},
#[name(scrolled_win)]
gtk::ScrolledWindow {
set_hexpand: true,
set_vexpand: true,
set_margin_all: 12,
add_css_class: "card",
set_overflow: gtk::Overflow::Hidden,
gtk::TextView {
set_hexpand: true,
set_vexpand: true,
set_editable: false,
set_monospace: true,
set_buffer: Some(&model.textbuf),
},
},
gtk::Button {
add_css_class: "pill",
set_halign: gtk::Align::Center,
set_label: "Close",
set_margin_all: 12,
#[track = "model.changed(BuildWindow::can_close())"]
set_sensitive: model.can_close,
connect_clicked[win] => move |_| {
win.hide();
},
}
}
}
}
fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
self.reset();
match message {
BuildWindowMsg::Present => {
self.win.as_ref().unwrap().present();
},
BuildWindowMsg::UpdateTitle(t) => {
self.set_title(t);
},
BuildWindowMsg::UpdateContent(c) => {
self.set_content(c);
self.textbuf.set_text(&self.content);
let sw = self.scrolled_win.as_ref().unwrap().clone();
let adj = sw.vadjustment();
// upper means highest value = lowest point in the sw
adj.set_value(adj.upper());
sw.set_vadjustment(Some(&adj));
},
BuildWindowMsg::UpdateCanClose(val) => {
self.set_can_close(val);
}
};
}
fn init(
init: Self::Init,
root: &Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
let mut model = BuildWindow {
tracker: 0,
title: "".into(),
content: "".into(),
can_close: false,
textbuf: gtk::TextBuffer::builder().build(),
win: None,
scrolled_win: None,
};
let widgets = view_output!();
model.scrolled_win = Some(widgets.scrolled_win.clone());
model.win = Some(widgets.win.clone());
ComponentParts { model, widgets }
}
}

172
src/ui/debug_view.rs Normal file
View file

@ -0,0 +1,172 @@
use gtk::prelude::*;
use relm4::prelude::*;
use relm4::{ComponentSender, SimpleComponent};
use relm4_icons::icon_name;
#[derive(Debug)]
pub enum DebugLevel {
Debug,
Info,
Warning,
Error,
}
impl DebugLevel {
pub fn from_string(s: String) -> Self {
match s.to_lowercase().as_str() {
"debug" => Self::Debug,
"info" => Self::Info,
"warning" => Self::Warning,
"error" => Self::Error,
_ => Self::Debug,
}
}
}
pub const DEBUG_LEVEL_STRINGS: [&str; 4] = ["Debug", "Info", "Warning", "Error"];
#[derive(Debug)]
pub enum DebugViewMsg {
DebugLevelChanged(DebugLevel),
SearchTermChanged(String),
LogUpdated(Vec<String>),
EnableDebugViewChanged(bool),
}
#[tracker::track]
pub struct DebugView {
#[tracker::do_not_track]
pub debug_level: DebugLevel,
#[tracker::do_not_track]
pub search_term: String,
#[tracker::do_not_track]
pub log: String,
#[tracker::do_not_track]
pub textbuf: gtk::TextBuffer,
#[tracker::do_not_track]
pub scrolled_win: Option<gtk::ScrolledWindow>,
#[tracker::do_not_track]
pub searchbar: Option<gtk::SearchBar>,
#[tracker::do_not_track]
pub search_entry: Option<gtk::SearchEntry>,
pub enable_debug_view: bool,
}
pub struct DebugViewInit {
pub enable_debug_view: bool,
}
#[relm4::component(pub)]
impl SimpleComponent for DebugView {
type Init = DebugViewInit;
type Input = DebugViewMsg;
type Output = ();
view! {
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_hexpand: true,
set_vexpand: true,
#[track = "model.changed(DebugView::enable_debug_view())"]
set_visible: model.enable_debug_view,
gtk::WindowHandle {
set_hexpand: true,
set_vexpand: false,
adw::HeaderBar {
#[wrap(Some)]
set_title_widget: title_label = &gtk::Label {
set_label: "Debug View",
add_css_class: "title",
},
pack_end: search_toggle = &gtk::ToggleButton {
set_icon_name: icon_name::LOUPE,
set_tooltip_text: Some("Filter Log"),
},
pack_start: log_level_dropdown = &gtk::DropDown::from_strings(&DEBUG_LEVEL_STRINGS) {
},
},
},
#[name(searchbar)]
gtk::SearchBar {
set_hexpand: true,
#[chain(flags(gtk::glib::BindingFlags::BIDIRECTIONAL).build())]
bind_property: ("search-mode-enabled", &search_toggle, "active"),
#[wrap(Some)]
set_child: search_entry = &gtk::SearchEntry {
set_hexpand: true,
},
connect_entry: &search_entry,
},
#[name(scrolled_win)]
gtk::ScrolledWindow {
set_hexpand: true,
set_vexpand: true,
gtk::TextView {
set_hexpand: true,
set_vexpand: true,
set_editable: false,
set_monospace: true,
set_buffer: Some(&model.textbuf)
},
}
}
}
fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {
self.reset();
match message {
DebugViewMsg::DebugLevelChanged(level) => {}
DebugViewMsg::SearchTermChanged(term) => {}
DebugViewMsg::LogUpdated(n_log) => {
let searchbar = self.searchbar.as_ref().unwrap().clone();
let search_entry = self.search_entry.as_ref().unwrap().clone();
let mut search_text: String = search_entry.text().into();
search_text = search_text.trim().to_lowercase();
if searchbar.is_search_mode() && !search_text.is_empty() {
self.log = n_log
.iter()
.filter(|row| row.to_lowercase().contains(&search_text))
.map(|s| s.to_string())
.collect::<Vec<String>>()
.concat();
} else {
self.log = n_log.concat();
}
self.textbuf.set_text(&self.log);
let sw = self.scrolled_win.as_ref().unwrap().clone();
let adj = sw.vadjustment();
// upper means highest value = lowest point in the sw
adj.set_value(adj.upper());
sw.set_vadjustment(Some(&adj));
}
DebugViewMsg::EnableDebugViewChanged(val) => self.set_enable_debug_view(val),
}
}
fn init(
init: Self::Init,
root: &Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
let mut model = DebugView {
tracker: 0,
debug_level: DebugLevel::Debug,
search_term: "".into(),
log: "".into(),
textbuf: gtk::TextBuffer::builder().build(),
enable_debug_view: init.enable_debug_view,
scrolled_win: None,
searchbar: None,
search_entry: None,
};
let widgets = view_output!();
model.scrolled_win = Some(widgets.scrolled_win.clone());
model.searchbar = Some(widgets.searchbar.clone());
model.search_entry = Some(widgets.search_entry.clone());
ComponentParts { model, widgets }
}
}

141
src/ui/main_view.rs Normal file
View file

@ -0,0 +1,141 @@
use gtk::prelude::*;
use relm4::prelude::*;
use relm4::{SimpleComponent, ComponentSender, ComponentParts};
use relm4_icons::icon_name;
use crate::config::Config;
use crate::constants::APP_NAME;
use crate::ui::app::{AboutAction, DebugViewToggleAction, BuildProfileAction};
#[tracker::track]
pub struct MainView {
monado_active: bool,
enable_debug_view: bool,
profile_names: Vec<String>,
}
#[derive(Debug)]
pub enum MainViewMsg {
StartStopClicked,
MonadoActiveChanged(bool),
EnableDebugViewChanged(bool),
UpdateProfileNames(Vec<String>),
}
#[derive(Debug)]
pub enum MainViewOutMsg {
UpdateView,
EnableDebugViewChanged(bool),
DoStartStopMonado,
ProfileSelected(String),
}
pub struct MainViewInit {
pub config: Config,
}
#[relm4::component(pub)]
impl SimpleComponent for MainView {
type Init = MainViewInit;
type Input = MainViewMsg;
type Output = MainViewOutMsg;
menu! {
app_menu: {
section! {
// value inside action is ignored
"_Debug View" => DebugViewToggleAction,
"_Build Profile" => BuildProfileAction,
},
section! {
"_About" => AboutAction,
}
}
}
view! {
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
#[track = "model.changed(MainView::enable_debug_view())"]
set_hexpand: !model.enable_debug_view,
set_vexpand: true,
set_size_request: (270, 350),
gtk::WindowHandle {
set_hexpand: true,
set_vexpand: false,
adw::HeaderBar {
#[wrap(Some)]
set_title_widget: title_label = &gtk::Label {
set_label: APP_NAME,
add_css_class: "title",
},
pack_end: menu_btn = &gtk::MenuButton {
set_icon_name: icon_name::MENU,
set_menu_model: Some(&app_menu),
},
pack_start: profiles_dropdown = &gtk::DropDown {
set_tooltip_text: Some("Profiles"),
#[track = "model.changed(MainView::profile_names())"]
set_model: Some(&{
let names: Vec<_> = model.profile_names.iter().map(String::as_str).collect();
gtk::StringList::new(&names)
}),
},
#[track = "model.changed(MainView::enable_debug_view())"]
set_show_end_title_buttons: !model.enable_debug_view,
},
},
gtk::Box {
set_spacing: 12,
set_margin_all: 12,
gtk::Button {
add_css_class: "pill",
add_css_class: "suggested-action",
add_css_class: "destructive-action",
set_hexpand: true,
set_halign: gtk::Align::Center,
#[track = "model.changed(MainView::monado_active())"]
set_class_active: ("suggested-action", !model.monado_active),
#[track = "model.changed(MainView::monado_active())"]
set_label: match model.monado_active {
true => "Stop",
false => "Start",
},
connect_clicked[sender] => move |_| {
sender.input(MainViewMsg::StartStopClicked)
}
},
}
}
}
fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
self.reset();
match message {
MainViewMsg::StartStopClicked => {
sender.output(MainViewOutMsg::DoStartStopMonado);
},
MainViewMsg::MonadoActiveChanged(active) => {
self.set_monado_active(active);
}
MainViewMsg::EnableDebugViewChanged(val) => {
self.set_enable_debug_view(val);
}
MainViewMsg::UpdateProfileNames(names) => {
self.set_profile_names(names);
}
}
}
fn init(init: Self::Init, root: &Self::Root, sender: ComponentSender<Self>) -> ComponentParts<Self> {
let model = MainView {
monado_active: false,
enable_debug_view: init.config.debug_view_enabled,
profile_names: vec![],
tracker: 0,
};
let widgets = view_output!();
ComponentParts { model, widgets }
}
}

View file

@ -1,300 +0,0 @@
use super::styles::bordered_container::BorderedContainer;
use crate::{
builders::build_monado::get_build_monado_runner,
config::{get_config, save_config, Config},
constants::APP_NAME,
profile::Profile,
profiles::valve_index::valve_index_profile,
runner::{Runner, RunnerStatus},
ui::widgets::widgets::vseparator,
};
use expect_dialog::ExpectDialog;
use iced::{
executor,
theme::{Button, Container},
time,
widget::{
button, checkbox, column, container, pick_list, row, scrollable, text, text_input, Space,
},
Alignment, Application, Command, Element, Length, Padding, Subscription, Theme,
};
use iced_aw::{Card, Modal};
use std::{
cell::Cell,
time::{Duration, Instant},
};
pub struct MainWin {
profiles: Vec<Profile>,
config: Config,
debug_search_term: String,
monado_runner: Cell<Runner>,
monado_active: bool,
monado_log: String,
build_monado_runner: Option<Runner>,
show_build_monado_modal: bool,
build_monado_log: String,
}
#[derive(Debug, Clone)]
pub enum Message {
Start,
Stop,
Tick,
ProfileChanged(Profile),
EditProfile,
DebugViewChanged(bool),
LogLevelChanged(String),
DebugSearchChanged(String),
LogUpdate(Instant),
InstallMonado,
CloseBuildMonadoModal,
}
impl MainWin {
fn get_profile_by_name(&self, name: &String) -> Option<&Profile> {
for profile in &self.profiles {
if &profile.name == name {
return Some(profile);
}
}
return None;
}
fn get_selected_profile(&self) -> Option<&Profile> {
self.get_profile_by_name(&self.config.selected_profile_name)
}
fn start_monado(&mut self) {
self.monado_runner.get_mut().terminate();
let profile = self.get_selected_profile().unwrap();
self.monado_runner
.set(Runner::monado_runner_from_profile(profile.clone()));
self.monado_runner.get_mut().start();
}
fn get_monado_log(&mut self) {
self.monado_log = match self.monado_runner.get_mut().status() {
RunnerStatus::Running => self.monado_runner.get_mut().get_output(),
RunnerStatus::Stopped => "Monado service inactive".into(),
}
}
fn install_monado(&mut self) {
self.build_monado_log = String::new();
let profile = match self.get_selected_profile() {
None => {
return;
}
Some(p) => p,
};
self.build_monado_runner = Some(get_build_monado_runner(profile.clone()));
self.build_monado_runner.as_mut().unwrap().start();
self.show_build_monado_modal = true;
self.build_monado_log = self.build_monado_runner.as_mut().unwrap().get_output();
}
}
impl Application for MainWin {
type Message = Message;
type Flags = ();
type Executor = executor::Default;
type Theme = Theme;
fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
let profiles = vec![valve_index_profile()];
let monado_runner = Cell::new(Runner::monado_runner_from_profile(
profiles.get(0).cloned().unwrap(),
));
(
Self {
// TODO: load profiles from disk somehow
profiles,
config: get_config(),
debug_search_term: "".into(),
monado_runner,
monado_active: false,
monado_log: "".into(),
build_monado_runner: None,
show_build_monado_modal: false,
build_monado_log: String::new(),
},
Command::none(),
)
}
fn title(&self) -> String {
return APP_NAME.into();
}
fn update(&mut self, msg: Message) -> Command<Message> {
match msg {
Message::Start => {
self.start_monado();
}
Message::Stop => {
self.monado_runner.get_mut().terminate();
}
Message::ProfileChanged(profile) => {
if self.config.selected_profile_name != profile.name {
self.config.selected_profile_name = profile.name;
save_config(&self.config).expect_dialog("Failed to save config");
}
}
Message::EditProfile => {
println!("Edit {}", self.get_selected_profile().unwrap())
}
Message::DebugViewChanged(nval) => {
self.config.debug_view_enabled = nval;
save_config(&self.config).expect_dialog("Failed to save config");
}
Message::DebugSearchChanged(term) => self.debug_search_term = term,
Message::InstallMonado => {
self.install_monado();
}
Message::CloseBuildMonadoModal => match &mut self.build_monado_runner {
None => {
self.show_build_monado_modal = false;
}
Some(runner) => match runner.status() {
RunnerStatus::Running => {}
RunnerStatus::Stopped => {
self.show_build_monado_modal = false;
}
},
},
Message::LogUpdate(_) => {
self.monado_active = match self.monado_runner.get_mut().status() {
RunnerStatus::Running => true,
RunnerStatus::Stopped => false,
};
self.get_monado_log();
if self.show_build_monado_modal {
match &mut self.build_monado_runner {
None => {}
Some(runner) => match runner.status() {
RunnerStatus::Running => {
self.build_monado_log = runner.get_output();
}
RunnerStatus::Stopped => {}
},
}
}
}
_ => println!("unhandled"),
};
Command::none()
}
fn subscription(&self) -> Subscription<Self::Message> {
time::every(Duration::from_millis(500)).map(Message::LogUpdate)
}
fn view(&self) -> Element<Message> {
let debug_view: Element<Message> = match self.config.debug_view_enabled {
false => Space::new(Length::Shrink, Length::Shrink).into(),
true => {
let debug_toolbar = container(row![
pick_list(
vec![
"Debug".to_string(),
"Info".into(),
"Warning".into(),
"Error".into(),
],
Some("Debug".into()),
Message::LogLevelChanged
),
Space::new(Length::Fill, Length::Shrink),
text_input("Search...", self.debug_search_term.as_str())
.on_input(Message::DebugSearchChanged),
])
.style(Container::Custom(Box::new(BorderedContainer)))
.padding(12);
let view = column![
// TODO: tab bar
container(
scrollable(text(self.monado_log.clone()))
.height(Length::Fill)
.width(Length::Fill)
)
// .style(Container::Custom(Box::new(RoundedBorderedContainer)))
.padding(6)
.height(Length::Fill),
debug_toolbar,
];
row![vseparator(), view]
.width(Length::FillPortion(3))
.into()
}
};
let toolbar = container(
row![
pick_list(
self.profiles.to_vec(),
self.get_selected_profile().cloned(),
Message::ProfileChanged
),
button("Edit").on_press(Message::EditProfile),
Space::new(Length::Fill, Length::Shrink),
checkbox(
"Debug View",
self.config.debug_view_enabled,
Message::DebugViewChanged
),
]
.align_items(Alignment::Center)
.spacing(3),
)
.padding(12)
.style(Container::Custom(Box::new(BorderedContainer)));
let monado_view = column![
match self.monado_active {
true => button("Stop")
.on_press(Message::Stop)
.style(Button::Destructive),
false => button("Start").on_press(Message::Start),
}
.padding(Padding::from([6, 24])),
scrollable(
column![button("DEBUG: install monado").on_press(Message::InstallMonado),]
.spacing(6)
) // device info goes here
]
.spacing(12)
.height(Length::Fill)
.padding(12)
.align_items(Alignment::Center);
let main_view = column![monado_view, toolbar,]
.width(Length::FillPortion(2))
.height(Length::Fill);
let win_content = row![main_view, debug_view,];
Modal::new(self.show_build_monado_modal, win_content, || {
Card::new(
text("Installing Monado..."),
column![scrollable(text(self.build_monado_log.clone())),].spacing(12),
)
.foot(row![
button("Close").on_press(Message::CloseBuildMonadoModal),
])
.max_width(600.0)
.max_height(400.0)
.into()
})
.into()
}
fn theme(&self) -> Self::Theme {
Theme::Dark
}
}

View file

@ -1,3 +1,5 @@
pub mod main_win;
pub mod widgets;
pub mod styles;
pub mod app;
pub mod main_view;
pub mod about_dialog;
pub mod debug_view;
pub mod build_window;