// Stiletto // Copyright (C) 2020 Stiletto Authors // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . use log::{info, warn}; use druid::commands; use druid::im::vector; use druid::widget::prelude::*; use druid::widget::{ Align, Button, CrossAxisAlignment, Flex, List, ListGrowDirection, SizedBox, WidgetExt, }; use druid::{ AppDelegate, AppLauncher, Color, Command, Data, DelegateCtx, Env, FileDialogOptions, FileSpec, Lens, LocalizedString, Target, WindowDesc, }; use im::Vector; use stiletto::history::VersionedCanvas; use stiletto::tool::{CanvasToolCtx, CanvasToolParams}; use stiletto::widget::{build_simple_tool_widget, CanvasState, CanvasToolIconState, CanvasWidget}; use stiletto::DocumentSnapshot; pub fn main() { let window = WindowDesc::new(build_ui) .window_size((1024.0, 1400.0)) .title( LocalizedString::new("custom-widget-demo-window-title").with_placeholder("Stiletto"), ); let default_pen_params = CanvasToolParams::Pen { thickness: 2.0, color: Color::rgb(0, 0, 0), }; let canvas_data = StilettoState { canvas: CanvasState { versioned_canvas: VersionedCanvas::new(vector![]), tool_ctx: CanvasToolCtx::new(default_pen_params.clone()), }, tool_icons: vector![ CanvasToolIconState { tool_params: default_pen_params, selected: true, }, CanvasToolIconState { tool_params: CanvasToolParams::Eraser, selected: false, } ], current_tool: 0, }; AppLauncher::with_window(window) .use_simple_logger() .delegate(Delegate) .launch(canvas_data) .expect("launch failed"); } #[derive(Clone, Data, Lens)] struct StilettoState { canvas: CanvasState, tool_icons: Vector, current_tool: usize, } fn build_ui() -> impl Widget { let history_buttons = Flex::row() .cross_axis_alignment(CrossAxisAlignment::Center) .with_child(Button::new("Undo").on_click( |_ctx: &mut EventCtx, data: &mut StilettoState, _env: &Env| data.canvas.perform_undo(), )) .with_child(Button::new("Redo").on_click( |_ctx: &mut EventCtx, data: &mut StilettoState, _env: &Env| data.canvas.perform_redo(), )); let stlt = FileSpec::new("Stiletto notebook", &["stlt"]); let save_dialog_options = FileDialogOptions::new() .allowed_types(vec![stlt]) .default_type(stlt); let open_dialog_options = save_dialog_options.clone(); let save_buttons = Flex::row() .cross_axis_alignment(CrossAxisAlignment::Center) .with_child(Button::new("Open").on_click(move |ctx, _, _| { ctx.submit_command( Command::new( druid::commands::SHOW_OPEN_PANEL, open_dialog_options.clone(), ), None, ) })) .with_child(Button::new("Save").on_click(move |ctx, _, _| { ctx.submit_command( Command::new( druid::commands::SHOW_SAVE_PANEL, save_dialog_options.clone(), ), None, ) })); let tool_buttons = Flex::row() .cross_axis_alignment(CrossAxisAlignment::Center) .with_flex_child( List::new(|| { build_simple_tool_widget(30.0, 30.0, 5.0) .padding((8.0, 0.0)) .on_click(|ctx, tool_state, _| { tool_state.selected = true; ctx.submit_command(stiletto::commands::UPDATE_TOOL, None); }) }) .grow(ListGrowDirection::Right) .lens(StilettoState::tool_icons), 1.0, ); let toolbar = Flex::row() .cross_axis_alignment(CrossAxisAlignment::Center) .with_spacer(30.0) .with_flex_child(Align::left(history_buttons), 1.0) .with_spacer(10.0) .with_flex_child(Align::left(tool_buttons), 2.0) .with_spacer(20.0) .with_flex_child(Align::right(save_buttons), 1.0) .with_spacer(30.0); Flex::column() .cross_axis_alignment(CrossAxisAlignment::Center) .must_fill_main_axis(true) .with_child(SizedBox::new(Align::left(toolbar)).height(50.0)) .with_flex_child((CanvasWidget {}).lens(StilettoState::canvas), 1.0) } struct Delegate; impl AppDelegate for Delegate { fn command( &mut self, _ctx: &mut DelegateCtx, _target: Target, cmd: &Command, data: &mut StilettoState, _env: &Env, ) -> bool { use std::fs::File; if let Some(Some(file_info)) = cmd.get(commands::SAVE_FILE) { let res_file = File::create(file_info.path()); if let Ok(f) = res_file { let write_res = serde_json::to_writer_pretty(f, &data.canvas.get_document_snapshot()); if write_res.is_err() { warn!("Error while saving: {:?}", write_res.err()); } else { info!("Written to file: {}", file_info.path().display()); } } else { warn!("Cannot create file: {:?}", res_file.err()); } return true; } if let Some(file_info) = cmd.get(commands::OPEN_FILE) { if let Ok(f) = File::open(file_info.path()) { let res_snapshot: Result = serde_json::from_reader(f); if let Ok(document_snapshot) = res_snapshot { data.canvas.set_from_snapshot(document_snapshot); info!("Loaded file {}", file_info.path().display()); } else { warn!("didn't work: {:?}", res_snapshot.err()); } } return true; } if cmd.get(stiletto::commands::UPDATE_TOOL).is_some() { let old_tool = data.current_tool; if let Some(new_tool) = data .tool_icons .iter() .enumerate() .position(|(pos, x)| pos != old_tool && x.selected) { info!("Changing active tool to: tool #{}", new_tool); data.tool_icons.get_mut(old_tool).unwrap().selected = false; data.current_tool = new_tool; data.canvas.set_tool_ctx(CanvasToolCtx::new( data.tool_icons.get(new_tool).unwrap().tool_params.clone(), )); } } false } }