First commit, partially implement about dialog
This commit is contained in:
commit
8b9adcddcc
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
target/
|
||||||
|
**/*.rs.bk
|
1235
Cargo.lock
generated
Normal file
1235
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "vgtk-todomvc"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Timothy Warren <twarren@nabancard.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
vgtk = "0.2"
|
||||||
|
pretty_env_logger = "0.4"
|
BIN
src/dog.png
Normal file
BIN
src/dog.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 254 KiB |
277
src/main.rs
Normal file
277
src/main.rs
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
#![recursion_limit = "1024"]
|
||||||
|
|
||||||
|
use vgtk::ext::*;
|
||||||
|
use vgtk::lib::gdk_pixbuf::Pixbuf;
|
||||||
|
use vgtk::lib::gio::{ActionExt, ApplicationFlags, Cancellable, MemoryInputStream, SimpleAction};
|
||||||
|
use vgtk::lib::glib::Bytes;
|
||||||
|
use vgtk::lib::gtk::*;
|
||||||
|
use vgtk::{gtk, gtk_if, run, Callback, Component, UpdateAction, VNode};
|
||||||
|
|
||||||
|
static DOG: &[u8] = include_bytes!("dog.png");
|
||||||
|
|
||||||
|
pub struct AboutDialog {
|
||||||
|
dog: Pixbuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AboutDialog {
|
||||||
|
fn default() -> Self {
|
||||||
|
let data_stream = MemoryInputStream::new_from_bytes(&Bytes::from_static(DOG));
|
||||||
|
let dog = Pixbuf::new_from_stream(&data_stream, None as Option<&Cancellable>).unwrap();
|
||||||
|
|
||||||
|
AboutDialog { dog }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct Radio {
|
||||||
|
pub labels: &'static [&'static str],
|
||||||
|
pub active: usize,
|
||||||
|
pub on_changed: Callback<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum RadioMessage {
|
||||||
|
Changed(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Task {
|
||||||
|
text: String,
|
||||||
|
done: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Radio {
|
||||||
|
type Message = RadioMessage;
|
||||||
|
type Properties = Self;
|
||||||
|
|
||||||
|
fn update(&mut self, msg: Self::Message) -> UpdateAction<Self> {
|
||||||
|
match msg {
|
||||||
|
RadioMessage::Changed(index) => {
|
||||||
|
self.on_changed.send(index);
|
||||||
|
UpdateAction::Render
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create(props: Self) -> Self {
|
||||||
|
props
|
||||||
|
}
|
||||||
|
|
||||||
|
fn change(&mut self, props: Self) -> UpdateAction<Self> {
|
||||||
|
*self = props;
|
||||||
|
UpdateAction::Render
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self) -> VNode<Self> {
|
||||||
|
gtk! {
|
||||||
|
<Box spacing=10>
|
||||||
|
{
|
||||||
|
self.labels.iter().enumerate().map(|(index, label)| gtk! {
|
||||||
|
<ToggleButton
|
||||||
|
label={ *label }
|
||||||
|
active={ index == self.active }
|
||||||
|
on toggled=|_| RadioMessage::Changed(index)
|
||||||
|
/>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Task {
|
||||||
|
fn new<S: ToString>(text: S, done: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
text: text.to_string(),
|
||||||
|
done,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn label(&self) -> String {
|
||||||
|
if self.done {
|
||||||
|
format!(
|
||||||
|
r#"<span strikethrough="true" alpha="50%">{}</span>"#,
|
||||||
|
self.text
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
self.text.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&self, index: usize) -> VNode<Model> {
|
||||||
|
gtk! {
|
||||||
|
<ListBoxRow>
|
||||||
|
<Box>
|
||||||
|
<CheckButton active=self.done on toggled=|_| Message::Toggle { index } />
|
||||||
|
<Label label=self.label() use_markup=true />
|
||||||
|
<Button
|
||||||
|
Box::pack_type=PackType::End
|
||||||
|
relief=ReliefStyle::None
|
||||||
|
label="Delete"
|
||||||
|
image="edit-delete"
|
||||||
|
on clicked=|_| Message::Delete { index }
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</ListBoxRow>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Model {
|
||||||
|
tasks: Vec<Task>,
|
||||||
|
filter: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Model {
|
||||||
|
fn default() -> Self {
|
||||||
|
Model {
|
||||||
|
tasks: vec![
|
||||||
|
Task::new("Call Joe", true),
|
||||||
|
Task::new("Call Mike", true),
|
||||||
|
Task::new("Call Robert", false),
|
||||||
|
Task::new("Get Robert to fix the bug", false),
|
||||||
|
],
|
||||||
|
filter: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Model {
|
||||||
|
fn count_completed(&self) -> usize {
|
||||||
|
self.tasks.iter().filter(|task| task.done).count()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn items_left(&self) -> String {
|
||||||
|
let left = self.tasks.iter().filter(|task| !task.done).count();
|
||||||
|
let plural = if left == 1 { "item" } else { "items" };
|
||||||
|
format!("{} {} left", left, plural)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_task(&self, task: &Task) -> bool {
|
||||||
|
match self.filter {
|
||||||
|
// "All"
|
||||||
|
0 => true,
|
||||||
|
// "Active"
|
||||||
|
1 => !task.done,
|
||||||
|
// "Completed"
|
||||||
|
2 => task.done,
|
||||||
|
// index out of bounds
|
||||||
|
__ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum Message {
|
||||||
|
About,
|
||||||
|
Exit,
|
||||||
|
Toggle { index: usize },
|
||||||
|
Add { task: String },
|
||||||
|
Delete { index: usize },
|
||||||
|
Filter { filter: usize },
|
||||||
|
Cleanup,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Model {
|
||||||
|
type Message = Message;
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn update(&mut self, msg: Self::Message) -> UpdateAction<Self> {
|
||||||
|
match msg {
|
||||||
|
Message::Exit => {
|
||||||
|
vgtk::quit();
|
||||||
|
UpdateAction::None
|
||||||
|
}
|
||||||
|
Message::Toggle { index } => {
|
||||||
|
self.tasks[index].done = !self.tasks[index].done;
|
||||||
|
UpdateAction::Render
|
||||||
|
}
|
||||||
|
Message::Add { task } => {
|
||||||
|
self.tasks.push(Task::new(task, false));
|
||||||
|
UpdateAction::Render
|
||||||
|
}
|
||||||
|
Message::Delete { index } => {
|
||||||
|
self.tasks.remove(index);
|
||||||
|
UpdateAction::Render
|
||||||
|
}
|
||||||
|
Message::Filter{ filter } => {
|
||||||
|
self.filter = filter;
|
||||||
|
UpdateAction::Render
|
||||||
|
}
|
||||||
|
Message::Cleanup => {
|
||||||
|
self.tasks.retain(|task| !task.done);
|
||||||
|
UpdateAction::Render
|
||||||
|
}
|
||||||
|
Message::About => UpdateAction::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self) -> VNode<Model> {
|
||||||
|
let main_menu = vgtk::menu()
|
||||||
|
.section(vgtk::menu().item("About...", "app.about"))
|
||||||
|
.section(vgtk::menu().item("Quit", "app.quit"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
gtk! {
|
||||||
|
<Application::new_unwrap(Some("com.example.vgtk-tutorial"), ApplicationFlags::empty())>
|
||||||
|
<SimpleAction::new("quit", None) Application::accels=["<Ctrl>q"].as_ref() enabled=true />
|
||||||
|
<SimpleAction::new("about", None) enabled=true on activate=|_,_| Message::About />
|
||||||
|
<Window default_width=800 default_height=600 border_width=20 on destroy=|_| Message::Exit>
|
||||||
|
<HeaderBar title="Erlang: The Todo List" show_close_button=true>
|
||||||
|
<MenuButton
|
||||||
|
HeaderBar::pack_type=PackType::End
|
||||||
|
@MenuButtonExt::direction=ArrowType::Down
|
||||||
|
relief=ReliefStyle::None
|
||||||
|
image="open-menu-symbolic"
|
||||||
|
>
|
||||||
|
<Menu::new_from_model(&main_menu) />
|
||||||
|
</MenuButton>
|
||||||
|
</HeaderBar>
|
||||||
|
<Box orientation=Orientation::Vertical spacing=10>
|
||||||
|
<Entry
|
||||||
|
placeholder_text="What needs to be done?"
|
||||||
|
on activate=|entry| {
|
||||||
|
entry.select_region(0, -1);
|
||||||
|
Message::Add {
|
||||||
|
task: entry.get_text().unwrap().to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ScrolledWindow Box::fill=true Box::expand=true>
|
||||||
|
<ListBox selection_mode=SelectionMode::None>
|
||||||
|
{
|
||||||
|
self.tasks.iter().filter(|task| self.filter_task(task))
|
||||||
|
.enumerate().map(|(index, task)| task.render(index))
|
||||||
|
}
|
||||||
|
</ListBox>
|
||||||
|
</ScrolledWindow>
|
||||||
|
<Box>
|
||||||
|
<Label label=self.items_left() />
|
||||||
|
<@Radio
|
||||||
|
Box::center_widget=true
|
||||||
|
active=self.filter
|
||||||
|
labels=["All","Active","Completed"].as_ref()
|
||||||
|
on changed=|filter| Message::Filter { filter }
|
||||||
|
/>
|
||||||
|
{
|
||||||
|
gtk_if!(self.count_completed() > 0 => {
|
||||||
|
<Button
|
||||||
|
label="Clear completed"
|
||||||
|
Box::pack_type=PackType::End
|
||||||
|
on clicked=|_| Message::Cleanup
|
||||||
|
/>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Window>
|
||||||
|
</Application>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
pretty_env_logger::init();
|
||||||
|
std::process::exit(run::<Model>());
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user