prevent all lights having their state set on startup

+ some cleanup
This commit is contained in:
LordMZTE 2021-11-08 21:50:29 +01:00
parent c610f8b798
commit c02ff988c1
5 changed files with 114 additions and 43 deletions

View file

@ -23,13 +23,14 @@ pub enum HeaderBarMsg {
#[widget] #[widget]
impl Widget for HeaderBar { impl Widget for HeaderBar {
fn model(data: (UnboundedSender<RuntimeMsg>, Sender<Msg>, String, String)) -> HeaderBarModel { fn model(data: (UnboundedSender<RuntimeMsg>, Sender<Msg>)) -> HeaderBarModel {
HeaderBarModel { HeaderBarModel {
settings: relm::init::<Settings>((data.0.clone(), data.2, data.3)) settings: relm::init::<Settings>(data.0.clone())
.expect("failed to create settings window"), .expect("failed to create settings window"),
rootwintx: data.1, rootwintx: data.1,
} }
} }
fn update(&mut self, msg: HeaderBarMsg) { fn update(&mut self, msg: HeaderBarMsg) {
match msg { match msg {
HeaderBarMsg::OpenSettings => { HeaderBarMsg::OpenSettings => {

View file

@ -1,8 +1,9 @@
use relm::Relm;
use angular_units::Turns; use angular_units::Turns;
use gtk::{prelude::*, Orientation}; use gtk::{prelude::*, Orientation};
use log::debug; use log::debug;
use prisma::{Hsv, Rgb}; use prisma::{Hsv, Rgb};
use relm::Widget; use relm::{Channel, Sender, Widget};
use relm_derive::{widget, Msg}; use relm_derive::{widget, Msg};
use rhue::{ use rhue::{
api::{Light, LightArchetype}, api::{Light, LightArchetype},
@ -14,6 +15,8 @@ use tokio::sync::mpsc::UnboundedSender;
use crate::runtime::RuntimeMsg; use crate::runtime::RuntimeMsg;
pub struct LightEntryModel { pub struct LightEntryModel {
_channel: Channel<LightEntryMsg>,
tx: Sender<LightEntryMsg>,
id: String, id: String,
name: String, name: String,
runtime: UnboundedSender<RuntimeMsg>, runtime: UnboundedSender<RuntimeMsg>,
@ -21,12 +24,18 @@ pub struct LightEntryModel {
brightness: f64, brightness: f64,
color: Rc<Cell<Rgb<f64>>>, color: Rc<Cell<Rgb<f64>>>,
icon: String, icon: String,
updates_active: bool,
} }
#[derive(Clone, Msg)] #[derive(Clone, Msg)]
pub enum LightEntryMsg { pub enum LightEntryMsg {
Update(StateUpdate), Update(StateUpdate),
PropertyChanged(EntryLightProperty), PropertyChanged(EntryLightProperty),
/// Changing the values of the light's GUI elements before this message is
/// sent has no effect. This is to prevent light update requests being
/// sent to the bridge while their values are being set programatically
/// in the widget's initialization
ActivateUpdates,
} }
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]
@ -37,8 +46,16 @@ pub enum EntryLightProperty {
#[widget] #[widget]
impl Widget for LightEntry { impl Widget for LightEntry {
fn model(params: (String, Light, UnboundedSender<RuntimeMsg>)) -> LightEntryModel { fn model(
relm: &Relm<Self>,
params: (String, Light, UnboundedSender<RuntimeMsg>),
) -> LightEntryModel {
let stream = relm.stream().clone();
let (ch, tx) = Channel::new(move |msg| stream.emit(msg));
LightEntryModel { LightEntryModel {
_channel: ch,
tx,
id: params.0, id: params.0,
name: params.1.name, name: params.1.name,
runtime: params.2, runtime: params.2,
@ -53,6 +70,7 @@ impl Widget for LightEntry {
.into(), .into(),
)), )),
icon: resource_of_archetype(params.1.config.archetype), icon: resource_of_archetype(params.1.config.archetype),
updates_active: false,
} }
} }
@ -80,11 +98,13 @@ impl Widget for LightEntry {
self.model.on = on; self.model.on = on;
} }
let _ = self if self.model.updates_active {
.model let _ = self
.runtime .model
.send(RuntimeMsg::UpdateLight(self.model.id.clone(), upd)); .runtime
}, .send(RuntimeMsg::UpdateLight(self.model.id.clone(), upd));
}
}
LightEntryMsg::PropertyChanged(prop) => { LightEntryMsg::PropertyChanged(prop) => {
debug!("updating light {}", &self.model.id); debug!("updating light {}", &self.model.id);
@ -110,11 +130,15 @@ impl Widget for LightEntry {
..Default::default() ..Default::default()
}; };
let _ = self if self.model.updates_active {
.model let _ = self
.runtime .model
.send(RuntimeMsg::UpdateLight(self.model.id.clone(), upd)); .runtime
}, .send(RuntimeMsg::UpdateLight(self.model.id.clone(), upd));
}
}
LightEntryMsg::ActivateUpdates => self.model.updates_active = true,
} }
} }
@ -133,6 +157,12 @@ impl Widget for LightEntry {
Inhibit(false) Inhibit(false)
}); });
// we need to send this throught the channel, instead of just setting
// `self.model.updates_active`, since messages of the widget's stream
// are only handled once the view has been fully initialized.
// This would result in the updates being activated before the state is set.
let _ = self.model.tx.send(LightEntryMsg::ActivateUpdates);
} }
view! { view! {

View file

@ -52,7 +52,7 @@ impl Widget for Win {
Model { Model {
_channel: ch, _channel: ch,
headerbar: relm::init::<HeaderBar>((params.0.clone(), tx.clone(), params.1, params.2)) headerbar: relm::init::<HeaderBar>((params.0.clone(), tx.clone()))
.expect("header init"), .expect("header init"),
runtime: params.0, runtime: params.0,
tx, tx,

View file

@ -23,28 +23,35 @@ pub enum SettingsMsg {
Discover, Discover,
Register, Register,
Save, Save,
SetAddr(Option<String>), SetAddr(SettingsDataResponse),
SetUsername(Result<String, String>), SetUsername(SettingsDataResponse),
}
#[derive(Clone)]
pub enum SettingsDataResponse {
FailedWithError(String),
FailedWithoutError,
Success(String),
Unset,
} }
#[widget] #[widget]
impl Widget for Settings { impl Widget for Settings {
fn model( fn model(relm: &Relm<Settings>, runtime: UnboundedSender<RuntimeMsg>) -> SettingsModel {
relm: &Relm<Settings>,
params: (UnboundedSender<RuntimeMsg>, String, String),
) -> SettingsModel {
let stream = relm.stream().clone(); let stream = relm.stream().clone();
let (ch, tx) = Channel::new(move |msg| stream.emit(msg)); let (ch, tx) = Channel::new(move |msg| stream.emit(msg));
let _ = runtime.send(RuntimeMsg::RequestConfig(tx.clone()));
SettingsModel { SettingsModel {
_channel: ch, _channel: ch,
runtime: params.0, runtime,
tx, tx,
status: String::new(), status: String::new(),
addr_spinning: false, addr_spinning: false,
username_spinning: false, username_spinning: false,
addr: params.1, addr: String::new(),
username: params.2, username: String::new(),
} }
} }
@ -81,25 +88,31 @@ impl Widget for Settings {
} }
}, },
SettingsMsg::SetAddr(addr) => { SettingsMsg::SetAddr(res) => {
self.model.addr_spinning = false; self.model.addr_spinning = false;
if let Some(addr) = addr { match res {
self.model.addr = addr; SettingsDataResponse::FailedWithoutError => {
} else { self.model.status = "Couldn't find a Bridge.".to_string()
self.model.status = "Couldn't find a Bridge.".to_string(); },
SettingsDataResponse::FailedWithError(e) => {
self.model.status = format!("Couldn't find a Bridge: {}", e)
},
SettingsDataResponse::Success(addr) => self.model.addr = addr,
SettingsDataResponse::Unset => {},
} }
}, },
SettingsMsg::SetUsername(username) => { SettingsMsg::SetUsername(res) => {
self.model.username_spinning = false; self.model.username_spinning = false;
match username { match res {
Ok(u) => { SettingsDataResponse::FailedWithoutError => {
self.model.username = u; self.model.status = "Failed to register.".to_string()
}, },
SettingsDataResponse::FailedWithError(e) => {
Err(e) => { self.model.status = format!("Failed to register: {}", e)
self.model.status = format!("Failed to register at bridge: {}", e);
}, },
SettingsDataResponse::Success(username) => self.model.username = username,
SettingsDataResponse::Unset => {},
} }
}, },
} }

View file

@ -4,7 +4,10 @@ use tokio::sync::{mpsc, RwLock};
use crate::{ use crate::{
config::{Config, ConfigData}, config::{Config, ConfigData},
gui::{settings::SettingsMsg, Msg}, gui::{
settings::{SettingsDataResponse, SettingsMsg},
Msg,
},
}; };
use log::{error, info}; use log::{error, info};
use miette::{miette, Context, IntoDiagnostic}; use miette::{miette, Context, IntoDiagnostic};
@ -123,20 +126,27 @@ impl Runtime {
RuntimeMsg::DiscoverBridge(tx) => { RuntimeMsg::DiscoverBridge(tx) => {
let addr = rhue::discover_bridge(Arc::clone(&http), Duration::from_secs(5)).await?; let addr = rhue::discover_bridge(Arc::clone(&http), Duration::from_secs(5)).await?;
tx.send(SettingsMsg::SetAddr(addr.map(|u| u.to_string()))) tx.send(SettingsMsg::SetAddr(match addr {
.into_diagnostic()?; Some(a) => SettingsDataResponse::Success(a.to_string()),
None => SettingsDataResponse::FailedWithoutError,
}))
.into_diagnostic()?;
}, },
RuntimeMsg::Register(url, tx) => { RuntimeMsg::Register(url, tx) => {
match Bridge::register(http, "GUE", url).await?.to_result() { match Bridge::register(http, "GUE", url).await?.to_result() {
Ok(b) => { Ok(b) => {
let username = b.username.clone(); let username = b.username.clone();
*bridge.write().await = Some(b); *bridge.write().await = Some(b);
tx.send(SettingsMsg::SetUsername(Ok(username))) tx.send(SettingsMsg::SetUsername(SettingsDataResponse::Success(
.into_diagnostic()?; username,
)))
.into_diagnostic()?;
}, },
Err(e) => { Err(e) => {
tx.send(SettingsMsg::SetUsername(Err(e.description))) tx.send(SettingsMsg::SetUsername(
.into_diagnostic()?; SettingsDataResponse::FailedWithError(e.description),
))
.into_diagnostic()?;
}, },
} }
}, },
@ -146,6 +156,22 @@ impl Runtime {
bridge.update_light(&id, upd).await?; bridge.update_light(&id, upd).await?;
} }
}, },
RuntimeMsg::RequestConfig(tx) => {
let cfg = config.read().await;
tx.send(SettingsMsg::SetAddr(match &cfg.data.bridge_addr {
Some(a) => SettingsDataResponse::Success(a.to_string()),
None => SettingsDataResponse::Unset,
}))
.into_diagnostic()?;
tx.send(SettingsMsg::SetUsername(match &cfg.data.bridge_username {
Some(a) => SettingsDataResponse::Success(a.clone()),
None => SettingsDataResponse::Unset,
}))
.into_diagnostic()?;
},
} }
Ok(false) Ok(false)
@ -159,4 +185,5 @@ pub enum RuntimeMsg {
DiscoverBridge(Sender<SettingsMsg>), DiscoverBridge(Sender<SettingsMsg>),
Register(Url, Sender<SettingsMsg>), Register(Url, Sender<SettingsMsg>),
UpdateLight(String, StateUpdate), UpdateLight(String, StateUpdate),
RequestConfig(Sender<SettingsMsg>),
} }