// 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, SizedBox, WidgetExt};
use druid::{
AppDelegate, AppLauncher, Color, Command, Data, DelegateCtx, Env, FileDialogOptions, FileSpec,
Handled, Lens, LocalizedString, Target, WindowDesc,
};
use im::Vector;
use stiletto::canvas::Canvas;
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.0, 0.0),
};
let default_pen_params_2 = CanvasToolParams::Pen {
thickness: 2.0,
color: Color::rgb(255.0, 0.0, 0.0),
};
let canvas_data = StilettoState {
canvas: CanvasState {
versioned_canvas: VersionedCanvas::new(Canvas {
elements: vector![],
}),
tool_ctx: CanvasToolCtx::new(default_pen_params.clone()),
},
tool_icons: vector![
CanvasToolIconState {
tool_params: default_pen_params,
selected: true,
},
CanvasToolIconState {
tool_params: default_pen_params_2,
selected: false,
},
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(),
Target::Auto,
))
}))
.with_child(Button::new("Save").on_click(move |ctx, _, _| {
ctx.submit_command(Command::new(
druid::commands::SHOW_SAVE_PANEL,
save_dialog_options.clone(),
Target::Auto,
))
}));
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);
})
})
.horizontal()
.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,
) -> Handled {
use std::fs::File;
if let Some(file_info) = cmd.get(commands::SAVE_FILE_AS) {
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 Handled::Yes;
}
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 Handled::Yes;
}
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(),
));
}
}
Handled::No
}
}