use std::time::Duration;
use gtk::prelude::{BoxExt, ButtonExt, GtkWindowExt, OrientableExt, WidgetExt};
use relm4::factory::{
AsyncFactoryComponent, AsyncFactorySender, AsyncFactoryVecDeque, DynamicIndex,
};
use relm4::loading_widgets::LoadingWidgets;
use relm4::{view, ComponentParts, ComponentSender, RelmApp, RelmWidgetExt, SimpleComponent};
#[derive(Debug)]
struct Counter {
value: u8,
}
#[derive(Debug)]
enum CounterMsg {
Increment,
Decrement,
}
#[derive(Debug)]
enum CounterOutput {
SendFront(DynamicIndex),
MoveUp(DynamicIndex),
MoveDown(DynamicIndex),
Remove(DynamicIndex),
}
#[relm4::factory(async)]
impl AsyncFactoryComponent for Counter {
type Init = u8;
type Input = CounterMsg;
type Output = CounterOutput;
type CommandOutput = ();
type ParentWidget = gtk::Box;
view! {
root = gtk::Box {
#[name(label)]
gtk::Label {
#[watch]
set_label: &self.value.to_string(),
set_width_chars: 3,
},
#[name(add_button)]
gtk::Button {
set_label: "+",
connect_clicked => CounterMsg::Increment,
},
#[name(remove_button)]
gtk::Button {
set_label: "-",
connect_clicked => CounterMsg::Decrement,
},
#[name(move_up_button)]
gtk::Button {
set_label: "Up",
connect_clicked[sender, index] => move |_| {
sender.output(CounterOutput::MoveUp(index.clone())).unwrap();
}
},
#[name(move_down_button)]
gtk::Button {
set_label: "Down",
connect_clicked[sender, index] => move |_| {
sender.output(CounterOutput::MoveDown(index.clone())).unwrap();
}
},
#[name(to_front_button)]
gtk::Button {
set_label: "To Start",
connect_clicked[sender, index] => move |_| {
sender.output(CounterOutput::SendFront(index.clone())).unwrap();
}
},
gtk::Button {
set_label: "Remove",
connect_clicked[sender, index] => move |_| {
sender.output(CounterOutput::Remove(index.clone())).unwrap();
}
}
}
}
fn init_loading_widgets(root: Self::Root) -> Option<LoadingWidgets> {
view! {
#[local]
root {
set_orientation: gtk::Orientation::Horizontal,
set_spacing: 10,
#[name(spinner)]
gtk::Spinner {
start: (),
set_hexpand: true,
set_halign: gtk::Align::Center,
set_height_request: 34,
}
}
}
Some(LoadingWidgets::new(root, spinner))
}
async fn init_model(
value: Self::Init,
_index: &DynamicIndex,
_sender: AsyncFactorySender<Self>,
) -> Self {
tokio::time::sleep(Duration::from_secs(1)).await;
Self { value }
}
async fn update(&mut self, msg: Self::Input, _sender: AsyncFactorySender<Self>) {
tokio::time::sleep(Duration::from_secs(1)).await;
match msg {
CounterMsg::Increment => {
self.value = self.value.wrapping_add(1);
}
CounterMsg::Decrement => {
self.value = self.value.wrapping_sub(1);
}
}
}
fn shutdown(&mut self, _widgets: &mut Self::Widgets, _output: relm4::Sender<Self::Output>) {
println!("Counter with value {} was destroyed", self.value);
}
}
struct App {
created_widgets: u8,
counters: AsyncFactoryVecDeque<Counter>,
}
#[derive(Debug)]
enum AppMsg {
AddCounter,
RemoveCounter,
SendFront(DynamicIndex),
MoveUp(DynamicIndex),
MoveDown(DynamicIndex),
Remove(DynamicIndex),
}
#[relm4::component]
impl SimpleComponent for App {
type Init = u8;
type Input = AppMsg;
type Output = ();
view! {
gtk::Window {
set_title: Some("Factory example"),
set_default_size: (300, 100),
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_spacing: 5,
set_margin_all: 5,
gtk::Button {
set_label: "Add counter",
connect_clicked => AppMsg::AddCounter,
},
gtk::Button {
set_label: "Remove counter",
connect_clicked => AppMsg::RemoveCounter,
},
#[local_ref]
counter_box -> gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_spacing: 5,
}
}
}
}
fn init(
counter: Self::Init,
root: Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
let counters = AsyncFactoryVecDeque::builder().launch_default().forward(
sender.input_sender(),
|output| match output {
CounterOutput::SendFront(index) => AppMsg::SendFront(index),
CounterOutput::MoveUp(index) => AppMsg::MoveUp(index),
CounterOutput::MoveDown(index) => AppMsg::MoveDown(index),
CounterOutput::Remove(index) => AppMsg::Remove(index),
},
);
let model = App {
created_widgets: counter,
counters,
};
let counter_box = model.counters.widget();
let widgets = view_output!();
ComponentParts { model, widgets }
}
fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
let mut counters_guard = self.counters.guard();
match msg {
AppMsg::AddCounter => {
counters_guard.push_back(self.created_widgets);
self.created_widgets = self.created_widgets.wrapping_add(1);
}
AppMsg::RemoveCounter => {
counters_guard.pop_back();
}
AppMsg::SendFront(index) => {
counters_guard.move_front(index.current_index());
}
AppMsg::MoveDown(index) => {
let index = index.current_index();
let new_index = index + 1;
if new_index < counters_guard.len() {
counters_guard.move_to(index, new_index);
}
}
AppMsg::MoveUp(index) => {
let index = index.current_index();
if index != 0 {
counters_guard.move_to(index, index - 1);
}
}
AppMsg::Remove(index) => {
counters_guard.remove(index.current_index());
}
}
}
}
fn main() {
let app = RelmApp::new("relm4.example.factory");
app.run::<App>(0);
}