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