Gleam + Relm4 foreign node POC over Erlang distribution
0

Configure Feed

Select the types of activity you want to include in your feed.

gleamtk / ui / src / app.rs
5.2 kB 175 lines
1//! Relm4 + libadwaita counter window, driven by distribution messages. 2 3use relm4::gtk; 4use relm4::gtk::glib; 5use relm4::gtk::prelude::*; 6use relm4::prelude::*; 7use relm4::{ComponentParts, ComponentSender, RelmWidgetExt, SimpleComponent}; 8 9// libadwaita widget traits (AdwApplicationWindowExt, etc.) 10use relm4::adw::prelude::*; 11 12use crate::dist::Outbound; 13use crate::OUTBOUND; 14 15#[derive(Debug)] 16pub enum AppMsg { 17 /// Controller handshake / first message received. 18 Connected, 19 SetTitle(String), 20 SetLabel(String), 21 SetCount(i64), 22 /// UI-local: increment and notify controller. 23 Increment, 24 Decrement, 25 /// Dist asked for pong. 26 PongRequested, 27 DistDied(String), 28 Quit, 29} 30 31pub struct AppModel { 32 title: String, 33 label: String, 34 count: i64, 35 status: String, 36} 37 38#[relm4::component(pub)] 39impl SimpleComponent for AppModel { 40 type Init = (); 41 type Input = AppMsg; 42 type Output = (); 43 44 view! { 45 #[root] 46 adw::ApplicationWindow { 47 #[watch] 48 set_title: Some(&model.title), 49 set_default_width: 420, 50 set_default_height: 280, 51 52 connect_close_request[sender] => move |_| { 53 emit_outbound(Outbound::WindowClosed); 54 sender.input(AppMsg::Quit); 55 glib::Propagation::Proceed 56 }, 57 58 #[wrap(Some)] 59 set_content = &adw::ToolbarView { 60 add_top_bar = &adw::HeaderBar {}, 61 62 #[wrap(Some)] 63 set_content = &gtk::Box { 64 set_orientation: gtk::Orientation::Vertical, 65 set_spacing: 16, 66 set_margin_all: 24, 67 set_halign: gtk::Align::Center, 68 set_valign: gtk::Align::Center, 69 70 gtk::Label { 71 #[watch] 72 set_label: &model.label, 73 add_css_class: "title-1", 74 }, 75 76 gtk::Label { 77 #[watch] 78 set_label: &format!("Count: {}", model.count), 79 add_css_class: "title-2", 80 }, 81 82 gtk::Box { 83 set_orientation: gtk::Orientation::Horizontal, 84 set_spacing: 12, 85 set_halign: gtk::Align::Center, 86 87 gtk::Button { 88 set_label: "", 89 add_css_class: "circular", 90 connect_clicked => AppMsg::Decrement, 91 }, 92 93 gtk::Button { 94 set_label: "Click me", 95 add_css_class: "suggested-action", 96 add_css_class: "pill", 97 connect_clicked => AppMsg::Increment, 98 }, 99 100 gtk::Button { 101 set_label: "+", 102 add_css_class: "circular", 103 connect_clicked => AppMsg::Increment, 104 }, 105 }, 106 107 gtk::Label { 108 #[watch] 109 set_label: &model.status, 110 add_css_class: "dim-label", 111 }, 112 }, 113 }, 114 } 115 } 116 117 fn init( 118 _init: Self::Init, 119 root: Self::Root, 120 sender: ComponentSender<Self>, 121 ) -> ComponentParts<Self> { 122 let _ = &sender; 123 let model = AppModel { 124 title: "gleamtk".into(), 125 label: "Waiting for Gleam controller…".into(), 126 count: 0, 127 status: "foreign node up · registered as gui".into(), 128 }; 129 130 let widgets = view_output!(); 131 ComponentParts { model, widgets } 132 } 133 134 fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) { 135 match msg { 136 AppMsg::Connected => { 137 self.status = "controller connected".into(); 138 } 139 AppMsg::SetTitle(t) => { 140 self.title = t; 141 } 142 AppMsg::SetLabel(l) => { 143 self.label = l; 144 } 145 AppMsg::SetCount(n) => { 146 self.count = n; 147 } 148 AppMsg::Increment => { 149 self.count += 1; 150 emit_outbound(Outbound::Clicked(self.count)); 151 } 152 AppMsg::Decrement => { 153 self.count -= 1; 154 emit_outbound(Outbound::CountChanged(self.count)); 155 } 156 AppMsg::PongRequested => { 157 emit_outbound(Outbound::Pong); 158 } 159 AppMsg::DistDied(err) => { 160 self.status = format!("dist error: {err}"); 161 } 162 AppMsg::Quit => { 163 relm4::main_application().quit(); 164 } 165 } 166 } 167} 168 169fn emit_outbound(msg: Outbound) { 170 if let Some(tx) = OUTBOUND.get() { 171 if let Err(e) = tx.try_send(msg) { 172 eprintln!("[app] outbound send failed: {e}"); 173 } 174 } 175}