Skip to content

Instantly share code, notes, and snippets.

@wolfiestyle
Created January 17, 2019 00:20
Show Gist options
  • Save wolfiestyle/f774e484dddea86347eb5d8717a1daa6 to your computer and use it in GitHub Desktop.
Save wolfiestyle/f774e484dddea86347eb5d8717a1daa6 to your computer and use it in GitHub Desktop.
FRP in Gtk proof of concept
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