First commit, partially implement about dialog

This commit is contained in:
Timothy Warren 2020-02-28 11:51:03 -05:00
commit 8b9adcddcc
5 changed files with 1523 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
target/
**/*.rs.bk

1235
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

9
Cargo.toml Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

277
src/main.rs Normal file
View 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>());
}