aboutsummaryrefslogtreecommitdiff
path: root/src/main.rs
blob: 9a085862115f5b1001e306de00334fdd844fffee (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//! # Graf Karto cartographer
//!
//! ### What is it exactly
//! Graf Karto is a table top role playing game (TTRPG) map creation tool that is optimised for real
//! time map interaction.
//!
//! ### Motivation
//! While there are certainly many TTRPG map creation tools for single user and multi user available
//! online and on the market, we felt that most of them lack features or are to unwieldy to seriously
//! consider for real time dungeon drawing, say for instance when drawing a map for an old school
//! revival style game. This is why Graf Karto is optimised for speed above pretty graphical features.
//! The aim is for the user not to have to think much about how they are going to achieve what they are
//! doing and how they are going to make it pretty, but should just be able to *do* it, to have time to
//! worry about other things. This does not mean that all maps created should visually be equivalent to
//! a steaming pile of hot garbage, but if the visuals should try to get in the way, usability and speed
//! takes precedence.

#![allow(dead_code)]
#![warn(missing_docs)]

#[macro_use]
extern crate log;

pub mod button;
pub mod cli;
pub mod colours;
pub mod config;
pub mod editor;
pub mod grid;
pub mod gui;
pub mod map;
pub mod math;
pub mod svg;
pub mod tool;
pub mod transform;
pub mod transformable;

use cli::CLI;
use config::Config;
use editor::Editor;
use float_cmp::F64Margin;
use gui::{DimensionIndicator, ToolSidebar};
use raylib::prelude::*;
use std::ffi::CString;
use std::io;
use transform::Transform;

/// Location of the file containing the style used for the raylib user interface.
pub const GUI_STYLE: &str = "assets/style/cyber.rgs";
/// Location of the graf karto configuration options file.
pub const CONFIG_FILE: &str = "config.ron";

/// The acceptable error that is used throughout the project for two floats to be considered equal.
/// If it is set too low, the editor might not work properly, if set too high, the granularity may
/// become too low for certain purposes.
pub const FLOAT_MARGIN: F64Margin = F64Margin {
    epsilon: 10000. * f64::EPSILON,
    ulps: 0,
};

fn main() {
    pretty_env_logger::init();

    let (mut rl, thread) = raylib::init().resizable().title("Hello there!").build();
    rl.set_target_fps(120);
    rl.set_exit_key(None);

    // Load the configuration file, if available.
    let config = match Config::from_file(CONFIG_FILE) {
        Ok(config) => config,
        Err(err) => {
            /* Create a default config file if it doesn't exist, otherwise leave the incorrectly
             * formatted/corrupted or otherwise unreadable file alone.
             */
            let config = Config::default();
            if err.kind() == io::ErrorKind::NotFound {
                warn!("Could not find a configuration file. Creating default.");
                config
                    .write_file(CONFIG_FILE)
                    .expect("Could not write config file.");
            } else {
                error!(
                    "Could not read configuration file: {}\nUsing defaults for this run.",
                    err
                );
            }

            config
        }
    };

    // Load the preferred gui style
    rl.gui_load_style(Some(
        &CString::new(GUI_STYLE).expect("Could not create C string from style file name"),
    ));

    let mut editor = Editor::new(&mut rl, &thread, config);
    let mut dimension_indicator = DimensionIndicator::new();
    let tool_sidebar = ToolSidebar::new(&mut rl, &thread);
    let mut cli = CLI::new();

    let mut transform = Transform::new();
    let mut last_mouse_pos = rl.get_mouse_position();
    while !rl.window_should_close() {
        let screen_width = rl.get_screen_width();
        let screen_height = rl.get_screen_height();

        // Move the canvas together with the mouse
        if rl.is_mouse_button_down(MouseButton::MOUSE_MIDDLE_BUTTON) {
            transform.move_by_px(&(rl.get_mouse_position() - last_mouse_pos).into());
        }
        // Update the last mouse position
        last_mouse_pos = rl.get_mouse_position();

        let mouse_wheel_move = rl.get_mouse_wheel_move();
        if mouse_wheel_move != 0 {
            // Zoom in for positive and zoom out for negative mouse wheel rotations.
            let scale_factor = if mouse_wheel_move > 0 { 1.2 } else { 1. / 1.2 };
            transform.try_zoom(
                &rl.get_mouse_position().into(),
                mouse_wheel_move.abs() as f64 * scale_factor,
            );
        }

        cli.update(&mut rl, &mut editor);
        dimension_indicator.update(editor.map_mut(), &mut rl);
        editor.update(
            &mut rl,
            &transform,
            ToolSidebar::mouse_captured(screen_height as u16, last_mouse_pos.into()),
        );

        // Drawing section
        {
            let mut d = rl.begin_drawing(&thread);
            d.clear_background(Color::BLACK);
            grid::draw_grid(&mut d, screen_width, screen_height, &transform);
            editor.map().draw(&mut d, &transform);

            editor.draw_tools(&mut d, &transform);
            gui::position_indicator_draw(&mut d, last_mouse_pos.into(), &transform);
            dimension_indicator.draw(&mut d, &transform);
            tool_sidebar.draw(screen_height as u16, &mut d, &mut editor);
            cli.draw(&mut d);
        }
    }
}