So long fltk build
This commit is contained in:
parent
9f4520e2c5
commit
ccf068f2ab
@ -1,12 +0,0 @@
|
||||
[package]
|
||||
name = "board-builder-impl-fltk"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
board-builder = { path = "../" }
|
||||
fltk = "1.3.6"
|
||||
state = "0.5.3"
|
||||
image = "0.24.2"
|
@ -1,318 +0,0 @@
|
||||
use fltk::*;
|
||||
use fltk::prelude::*;
|
||||
use fltk::enums::*;
|
||||
use fltk::image::PngImage;
|
||||
|
||||
extern crate image;
|
||||
use image::io::Reader as ImageReader;
|
||||
use image::{ DynamicImage };
|
||||
|
||||
use state::Storage;
|
||||
|
||||
use std::sync::Mutex;
|
||||
|
||||
use board_builder::Board;
|
||||
use board_builder::{ encode_png, write_board_to_file, read_board_from_file, CoordTransformer };
|
||||
|
||||
//////////////////// Global State ////////////////////
|
||||
// Don't @ me...
|
||||
static STATE: Storage<Mutex<AppState>> = Storage::new();
|
||||
|
||||
//////////////////// Auxilary Dialogs ////////////////////
|
||||
fn add_remove_labels_dialog() {
|
||||
let state = STATE.get().lock().unwrap();
|
||||
let mut win = window::Window::default()
|
||||
.with_size(300, 200)
|
||||
.center_of_parent();
|
||||
let flex = group::Flex::default()
|
||||
.size_of_parent()
|
||||
.center_of_parent();
|
||||
let mut col = group::Flex::default().column();
|
||||
let mut label_key_browser = browser::HoldBrowser::default();
|
||||
for key in state.board.labels.keys() {
|
||||
label_key_browser.add(key);
|
||||
}
|
||||
let brow = group::Flex::default();
|
||||
button::Button::default().with_label("-");
|
||||
button::Button::default().with_label("+");
|
||||
brow.end();
|
||||
col.set_size(&brow, 30);
|
||||
col.end();
|
||||
let mut label_value_browser = browser::HoldBrowser::default();
|
||||
label_key_browser.set_callback({
|
||||
let labels = state.board.labels.clone();
|
||||
move |k| {
|
||||
label_value_browser.clear();
|
||||
if let Some(key) = k.selected_text() {
|
||||
for val in &labels[&key] {
|
||||
label_value_browser.add(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
flex.end();
|
||||
win.end();
|
||||
win.make_resizable(true);
|
||||
win.make_modal(true);
|
||||
win.show();
|
||||
while win.shown() {
|
||||
app::wait();
|
||||
}
|
||||
}
|
||||
|
||||
fn node_create_dialog(pos_x: f32, pos_y: f32) {
|
||||
let mut win = window::Window::default()
|
||||
.with_size(100, 100)
|
||||
.with_pos(app::event_x_root(), app::event_y_root());
|
||||
let flex = group::Flex::default()
|
||||
.column()
|
||||
.size_of_parent()
|
||||
.center_of_parent();
|
||||
frame::Frame::default().with_label("Name:");
|
||||
let name = input::Input::default();
|
||||
let mut btn = button::Button::default()
|
||||
.with_label("Create");
|
||||
flex.end();
|
||||
win.end();
|
||||
win.make_resizable(true);
|
||||
win.make_modal(true);
|
||||
win.show();
|
||||
btn.set_callback({
|
||||
let mut win = win.clone();
|
||||
move |_| {
|
||||
let mut state = STATE.get().lock().unwrap();
|
||||
state.board.add_node(pos_x, pos_y, name.value());
|
||||
win.hide();
|
||||
}
|
||||
});
|
||||
while win.shown() {
|
||||
app::wait();
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////// Dispatching Functions ////////////////////
|
||||
|
||||
mod dispatch {
|
||||
use super::*;
|
||||
type Coords = (f32, f32);
|
||||
pub(super) fn node_press(coords: Coords) {
|
||||
let (pos_x, pos_y) = coords;
|
||||
if app::event_button() == 1 {
|
||||
node_create_dialog(pos_x, pos_y);
|
||||
} else if app::event_button() == 3 {
|
||||
let mut state = STATE.get().lock().unwrap();
|
||||
let id = state.board.nearest_node(pos_x, pos_y);
|
||||
if let Some(id) = id {
|
||||
state.board.remove_node(id);
|
||||
}
|
||||
}
|
||||
app::redraw();
|
||||
}
|
||||
pub(super) fn edge_press(coords: Coords) {
|
||||
let (pos_x, pos_y) = coords;
|
||||
let mut state = STATE.get().lock().unwrap();
|
||||
if app::event_button() == 1 {
|
||||
match (state.selected_node, state.board.nearest_node(pos_x, pos_y)) {
|
||||
(Some(selected), Some(nearest)) => {
|
||||
if selected == nearest {
|
||||
state.selected_node = None;
|
||||
} else {
|
||||
state.board.add_edge(selected, nearest);
|
||||
}
|
||||
app::redraw();
|
||||
}
|
||||
(None, Some(nearest)) => {
|
||||
state.selected_node = Some(nearest);
|
||||
app::redraw();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FrameView<'a>(&'a frame::Frame);
|
||||
struct Pos(i32, i32);
|
||||
|
||||
impl<'a> FrameView<'a> {
|
||||
fn origin(&self) -> Pos {
|
||||
Pos(self.0.x(), self.0.y())
|
||||
}
|
||||
|
||||
fn extremes(&self) -> Pos {
|
||||
Pos(self.0.x() + self.0.w(), self.0.y() + self.0.h())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> CoordTransformer<Pos> for FrameView<'a> {
|
||||
fn origin(&self) -> Pos { self.origin() }
|
||||
fn extremes(&self) -> Pos { self.extremes() }
|
||||
}
|
||||
|
||||
impl From<(f32, f32)> for Pos {
|
||||
fn from((x, y): (f32, f32)) -> Self {
|
||||
Pos(x as i32, y as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Pos> for (f32, f32) {
|
||||
fn from(Pos(x, y): Pos) -> Self {
|
||||
(x as f32, y as f32)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//////////////////// App State ////////////////////
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum EditMode {
|
||||
Node,
|
||||
Edge,
|
||||
}
|
||||
struct AppState {
|
||||
pub board: Board,
|
||||
pub image_raw: Option<DynamicImage>,
|
||||
pub image: Option<PngImage>,
|
||||
pub selected_node: Option<usize>,
|
||||
pub edit_mode: EditMode,
|
||||
}
|
||||
impl AppState {
|
||||
fn new() -> Self {
|
||||
AppState {
|
||||
board: Board::new(),
|
||||
image_raw: None,
|
||||
image: None,
|
||||
selected_node: None,
|
||||
edit_mode: EditMode::Node
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////// Procedural App Creation ////////////////////
|
||||
|
||||
fn main() {
|
||||
// State
|
||||
STATE.set(Mutex::new(AppState::new()));
|
||||
// App setup
|
||||
let app = app::App::default()
|
||||
.with_scheme(app::Scheme::Gtk);
|
||||
let mut win = window::Window::default()
|
||||
.with_size(400, 300)
|
||||
.with_label("Board Builder");
|
||||
let mut flex = group::Flex::default()
|
||||
.size_of_parent();
|
||||
flex.set_type(group::FlexType::Column);
|
||||
|
||||
// Menu
|
||||
let mut menubar = menu::MenuBar::default();
|
||||
menubar.add("File/New", Shortcut::None, menu::MenuFlag::Normal, move |_| {
|
||||
*STATE.get().lock().unwrap() = AppState::new();
|
||||
app::redraw();
|
||||
});
|
||||
menubar.add("File/Open...", Shortcut::None, menu::MenuFlag::Normal, move |_| {
|
||||
let mut state = AppState::new();
|
||||
let mut fc = dialog::NativeFileChooser::new(dialog::NativeFileChooserType::BrowseFile);
|
||||
fc.show();
|
||||
|
||||
let (board, raw_image) = read_board_from_file(&fc.filename()).unwrap();
|
||||
|
||||
state.board = board;
|
||||
state.image_raw = raw_image;
|
||||
if let Some(image) = state.image_raw.as_ref() {
|
||||
state.image = Some(PngImage::from_data(&encode_png(image)).unwrap());
|
||||
} else {
|
||||
state.image = None;
|
||||
}
|
||||
*STATE.get().lock().unwrap() = state;
|
||||
app::redraw();
|
||||
});
|
||||
menubar.add("File/Open Image...", Shortcut::None, menu::MenuFlag::Normal, move |_| {
|
||||
let mut fc = dialog::NativeFileChooser::new(dialog::NativeFileChooserType::BrowseFile);
|
||||
fc.show();
|
||||
let filename = fc.filename();
|
||||
|
||||
match ImageReader::open(filename).map(|i| i.decode()) {
|
||||
Ok(Ok(image)) => {
|
||||
let mut state = STATE.get().lock().unwrap();
|
||||
let data = encode_png(&image);
|
||||
state.image = Some(PngImage::from_data(&data).unwrap());
|
||||
state.image_raw = Some(image);
|
||||
app::redraw();
|
||||
}
|
||||
_ => dialog::alert_default("Error opening file"),
|
||||
}
|
||||
});
|
||||
menubar.add("File/Save As ...", Shortcut::None, menu::MenuFlag::Normal, move |_| {
|
||||
let state = STATE.get().lock().unwrap();
|
||||
let mut fc = dialog::NativeFileChooser::new(dialog::NativeFileChooserType::BrowseSaveFile);
|
||||
fc.show();
|
||||
let filename = fc.filename();
|
||||
write_board_to_file(&state.board, state.image_raw.as_ref(), &filename).unwrap();
|
||||
});
|
||||
menubar.add("Edit/Edit Nodes", Shortcut::None, menu::MenuFlag::Normal, |_| {
|
||||
let mut state = STATE.get().lock().unwrap();
|
||||
state.edit_mode = EditMode::Node;
|
||||
});
|
||||
menubar.add("Edit/Edit Edges", Shortcut::None, menu::MenuFlag::Normal, |_| {
|
||||
let mut state = STATE.get().lock().unwrap();
|
||||
state.edit_mode = EditMode::Edge;
|
||||
});
|
||||
menubar.add("Edit/Edit Labels", Shortcut::None, menu::MenuFlag::Normal, |_| add_remove_labels_dialog());
|
||||
|
||||
flex.set_size(&menubar, 40);
|
||||
|
||||
// Canvas
|
||||
let mut frame = frame::Frame::default();
|
||||
frame.draw(move |f| {
|
||||
use draw::*;
|
||||
let mut state = STATE.get().lock().unwrap();
|
||||
// Background
|
||||
let image = &mut state.image;
|
||||
if let Some(image) = image.as_mut() {
|
||||
image.scale(f.w(), f.h(), false, true);
|
||||
image.draw(f.x(), f.y(), f.w(), f.h());
|
||||
}
|
||||
// Nodes
|
||||
let board = &state.board;
|
||||
let f = FrameView(f);
|
||||
for (&id, node) in &board.nodes {
|
||||
// Draw the node
|
||||
let Pos(x, y) = f.inv_xform(node.x, node.y);
|
||||
if state.selected_node == Some(id) {
|
||||
set_draw_color(Color::Red);
|
||||
draw_text(&node.name, x, y);
|
||||
set_draw_color(Color::Black);
|
||||
} else {
|
||||
draw_text(&node.name, x, y);
|
||||
}
|
||||
// Draw edges
|
||||
for other_id in &node.edges {
|
||||
let other = board.nodes.get(other_id).unwrap();
|
||||
let Pos(x1, y1) = f.inv_xform(other.x, other.y);
|
||||
draw_line(x, y, x1, y1);
|
||||
}
|
||||
}
|
||||
});
|
||||
frame.handle(move |f, e| {
|
||||
match e {
|
||||
Event::Push => {
|
||||
let f = FrameView(f);
|
||||
let edit_mode = STATE.get().lock().unwrap().edit_mode;
|
||||
let coords = f.xform(Pos(app::event_x(), app::event_y()));
|
||||
|
||||
match edit_mode {
|
||||
EditMode::Node => dispatch::node_press(coords),
|
||||
EditMode::Edge => dispatch::edge_press(coords),
|
||||
}
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
});
|
||||
|
||||
flex.end();
|
||||
win.end();
|
||||
win.make_resizable(true);
|
||||
win.show();
|
||||
app.run().unwrap();
|
||||
}
|
Loading…
Reference in New Issue
Block a user