Created
January 17, 2019 00:20
-
-
Save wolfiestyle/f774e484dddea86347eb5d8717a1daa6 to your computer and use it in GitHub Desktop.
FRP in Gtk proof of concept
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use fragile::Fragile; | |
use frappe::{Signal, Sink, Stream}; | |
use glib; | |
use gtk; | |
use gtk::prelude::*; | |
use std::thread; | |
use std::time::Duration; | |
fn main() { | |
gtk::init().unwrap(); | |
// build the Gtk UI | |
let window = gtk::Window::new(gtk::WindowType::Toplevel); | |
window.set_default_size(300, 300); | |
window.set_border_width(10); | |
window.set_title("gtktest"); | |
let (listbox, cmd_add, cmd_del, lbl_count); | |
window.add(&{ | |
let container = gtk::Box::new(gtk::Orientation::Vertical, 5); | |
container.add(&{ | |
let scrolled = gtk::ScrolledWindow::new(None, None); | |
scrolled.set_vexpand(true); | |
listbox = gtk::ListBox::new(); | |
scrolled.add(&listbox); | |
scrolled | |
}); | |
container.add(&{ | |
let cmdrow = gtk::Box::new(gtk::Orientation::Horizontal, 3); | |
cmd_add = gtk::Button::new_with_label("Add item"); | |
cmdrow.add(&cmd_add); | |
cmd_del = gtk::Button::new_with_label("Remove item"); | |
cmdrow.add(&cmd_del); | |
lbl_count = gtk::Label::new("0"); | |
cmdrow.pack_end(&lbl_count, true, true, 0); | |
cmdrow | |
}); | |
container | |
}); | |
window.connect_delete_event(|_, _| { | |
gtk::main_quit(); | |
Inhibit(false) | |
}); | |
// handle the events with FRP | |
let add_clicked = cmd_add.clicked_events(); | |
let counter = add_clicked | |
.fold(0, |a, _| a + 1) | |
.snapshot(&add_clicked, |a, _| a); | |
let counter_str = counter.map(|n| n.to_string()); | |
let counter_labels = counter | |
.map_n(|v, sender| { | |
let n = *v; | |
thread::spawn(move || { | |
thread::sleep(Duration::from_millis(1000)); | |
sender.send(n); | |
}); | |
}) | |
.to_main_thread() | |
.map(|a| { | |
let label = gtk::Label::new(format!("counter {}", a).as_str()); | |
label.show(); | |
label | |
}); | |
lbl_count.set_text_from_stream(&counter_str); | |
listbox.add_from_stream(&counter_labels); | |
let del_clicked = cmd_del.clicked_events(); | |
let _deleted = listbox | |
.selected_row() | |
.snapshot(&del_clicked, |a, _| a) | |
.filter_some() | |
.map(|w| w.get().destroy()); | |
window.show_all(); | |
gtk::main(); | |
} | |
// -- all of this will go into a library someday -- | |
/// Extension trait for gtk buttons. | |
trait FrpButtonExt { | |
/// Returns a `clicked` event stream. | |
fn clicked_events(&self) -> Stream<()>; | |
} | |
impl<T> FrpButtonExt for T | |
where | |
T: gtk::ButtonExt, | |
{ | |
fn clicked_events(&self) -> Stream<()> { | |
let sink = Sink::new(); | |
let stream = sink.stream(); | |
self.connect_clicked(move |_| sink.send(())); | |
stream | |
} | |
} | |
/// Extension trait for gtk containers. | |
trait FrpContainerExt { | |
/// Adds the widgets received from a Stream. | |
fn add_from_stream<W: IsA<gtk::Widget>>(&self, stream: &Stream<W>); | |
} | |
impl<T> FrpContainerExt for T | |
where | |
T: gtk::ContainerExt + Clone + 'static, | |
{ | |
fn add_from_stream<W: IsA<gtk::Widget>>(&self, stream: &Stream<W>) { | |
let wrapper = Fragile::new(self.clone()); | |
stream.observe(move |widget| { | |
wrapper.get().add(widget.as_ref()); | |
}) | |
} | |
} | |
/// Extension trait for gtk labels. | |
trait FrpLabelExt { | |
fn set_text_from_stream(&self, stream: &Stream<String>); | |
} | |
impl<T> FrpLabelExt for T | |
where | |
T: gtk::LabelExt + Clone + 'static, | |
{ | |
fn set_text_from_stream(&self, stream: &Stream<String>) { | |
let wrapper = Fragile::new(self.clone()); | |
stream.observe(move |s| wrapper.get().set_text(&s)) | |
} | |
} | |
/// Extension trait for gtk listbox. | |
trait FrpListBoxExt { | |
fn selected_row(&self) -> Signal<Option<Fragile<gtk::ListBoxRow>>>; | |
} | |
impl<T> FrpListBoxExt for T | |
where | |
T: gtk::ListBoxExt + Clone + 'static, | |
{ | |
fn selected_row(&self) -> Signal<Option<Fragile<gtk::ListBoxRow>>> { | |
let wrapper = Fragile::new(self.clone()); | |
Signal::from_fn(move || wrapper.get().get_selected_row().map(Fragile::new)) | |
} | |
} | |
/// Extension trait for frappe streams. | |
trait StreamExt<T> { | |
/// Executes the rest of this stream chain on the main thread. | |
fn to_main_thread(&self) -> Stream<T> | |
where | |
T: Clone + Send + 'static; | |
} | |
impl<T> StreamExt<T> for Stream<T> { | |
fn to_main_thread(&self) -> Stream<T> | |
where | |
T: Clone + Send + 'static, | |
{ | |
self.map_n(|val, sender| { | |
let mut val = Some(val.into_owned()); | |
glib::idle_add(move || { | |
sender.send(val.take().unwrap()); | |
Continue(false) | |
}); | |
}) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment