rx/
session.rs

1///! Session
2use crate::brush::*;
3use crate::cmd::{self, Command, CommandLine, Key, KeyMapping, Op, Value};
4use crate::color;
5use crate::data;
6use crate::event::{Event, TimedEvent};
7use crate::execution::{DigestMode, DigestState, Execution};
8use crate::hashmap;
9use crate::palette::*;
10use crate::platform::{self, InputState, KeyboardInput, LogicalSize, ModifiersState};
11use crate::resources::ResourceManager;
12use crate::view::{FileStatus, View, ViewCoords, ViewId, ViewManager};
13
14use rgx::core::{Blending, PresentMode, Rect};
15use rgx::kit::shape2d::{Fill, Rotation, Shape, Stroke};
16use rgx::kit::{Origin, Rgba8, ZDepth};
17use rgx::math::*;
18
19use directories as dirs;
20
21use std::cell::RefCell;
22use std::collections::{HashMap, HashSet};
23use std::fmt;
24use std::fs::File;
25use std::io;
26use std::ops::{Add, Deref, Sub};
27use std::path::{Path, PathBuf};
28use std::rc::Rc;
29use std::str::FromStr;
30use std::time;
31
32/// Help string.
33pub const HELP: &str = r#"
34:help                    Toggle this help
35:e <path..>              Edit path(s)
36:w [<path>]              Write view / Write view as <path>
37:q                       Quit view
38:q!                      Force quit view
39:echo <val>              Echo a value
40:echo "pixel!"           Echo the string "pixel!"
41:set <setting> = <val>   Set <setting> to <val>
42:set <setting>           Set <setting> to `on`
43:unset <setting>         Set <setting> to `off`
44:toggle <setting>        Toggle <setting> `on` / `off`
45:slice <n>               Slice view into <n> frames
46:source <path>           Source an rx script (eg. a palette or config)
47:map <key> <command>     Map a key combination to a command
48:f/resize <w> <h>        Resize frames
49:f/add                   Add a blank frame to the view
50:f/remove                Remove the last frame of the view
51:f/clone <index>         Clone frame <index> and add it to the view
52:f/clone                 Clone the last frame and add it to the view
53:p/clear                 Clear the palette
54:p/add <color>           Add <color> to the palette, eg. #ff0011
55:brush/set <mode>        Set brush mode, eg. `xsym` and `ysym` for symmetry
56:brush/unset <mode>      Unset brush mode
57
58SETTINGS
59
60debug             on/off             Debug mode
61checker           on/off             Alpha checker toggle
62vsync             on/off             Vertical sync toggle
63input/delay       0.0..32.0          Delay between render frames (ms)
64scale             1.0..4.0           UI scale
65animation         on/off             View animation toggle
66animation/delay   1..1000            View animation delay (ms)
67background        #000000..#ffffff   Set background appearance to <color>, eg. #ff0011
68"#;
69
70/// An RGB 8-bit color. Used when the alpha value isn't used.
71#[repr(C)]
72#[derive(Copy, Clone)]
73pub struct Rgb8 {
74    r: u8,
75    g: u8,
76    b: u8,
77}
78
79impl From<Rgba8> for Rgb8 {
80    fn from(rgba: Rgba8) -> Self {
81        Self {
82            r: rgba.r,
83            g: rgba.g,
84            b: rgba.b,
85        }
86    }
87}
88
89impl fmt::Display for Rgb8 {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        write!(f, "#{:02X}{:02X}{:02X}", self.r, self.g, self.b)
92    }
93}
94
95#[derive(Copy, Clone, Debug)]
96enum InternalCommand {
97    StopRecording,
98}
99
100/// Session coordinates.
101/// Encompasses anything within the window, such as the cursor position.
102#[derive(Copy, Clone, PartialEq, Debug)]
103pub struct SessionCoords(Point2<f32>);
104
105impl SessionCoords {
106    pub fn new(x: f32, y: f32) -> Self {
107        Self(Point2::new(x, y))
108    }
109
110    pub fn floor(&mut self) -> Self {
111        Self(self.0.map(f32::floor))
112    }
113}
114
115impl Deref for SessionCoords {
116    type Target = Point2<f32>;
117
118    fn deref(&self) -> &Point2<f32> {
119        &self.0
120    }
121}
122
123impl Add<Vector2<f32>> for SessionCoords {
124    type Output = Self;
125
126    fn add(self, vec: Vector2<f32>) -> Self {
127        SessionCoords(self.0 + vec)
128    }
129}
130
131impl Sub<Vector2<f32>> for SessionCoords {
132    type Output = Self;
133
134    fn sub(self, vec: Vector2<f32>) -> Self {
135        SessionCoords(self.0 - vec)
136    }
137}
138
139///////////////////////////////////////////////////////////////////////////////
140
141/// An editing mode the `Session` can be in.
142/// Some of these modes are inspired by vi.
143#[derive(Eq, PartialEq, Copy, Clone, Debug)]
144pub enum Mode {
145    /// Allows the user to paint pixels.
146    Normal,
147    /// Allows pixels to be selected, copied and manipulated visually.
148    Visual(VisualState),
149    /// Allows commands to be run.
150    Command,
151    /// Used to present work.
152    #[allow(dead_code)]
153    Present,
154    /// Activated with the `:help` command.
155    Help,
156}
157
158impl Default for Mode {
159    fn default() -> Self {
160        Mode::Normal
161    }
162}
163
164impl fmt::Display for Mode {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        match self {
167            Self::Normal => "normal".fmt(f),
168            Self::Visual(VisualState::Selecting { dragging: true }) => "visual (dragging)".fmt(f),
169            Self::Visual(VisualState::Selecting { .. }) => "visual".fmt(f),
170            Self::Visual(VisualState::Pasting) => "visual (pasting)".fmt(f),
171            Self::Command => "command".fmt(f),
172            Self::Present => "present".fmt(f),
173            Self::Help => "help".fmt(f),
174        }
175    }
176}
177
178#[derive(Eq, PartialEq, Copy, Clone, Debug)]
179pub enum VisualState {
180    Selecting { dragging: bool },
181    Pasting,
182}
183
184impl VisualState {
185    pub fn selecting() -> Self {
186        Self::Selecting { dragging: false }
187    }
188}
189
190impl Default for VisualState {
191    fn default() -> Self {
192        Self::selecting()
193    }
194}
195
196/// A pixel selection within a view.
197#[derive(Eq, PartialEq, Copy, Clone, Debug)]
198pub struct Selection(Rect<i32>);
199
200impl Selection {
201    /// Create a new selection from a rectangle.
202    pub fn new(x1: i32, y1: i32, x2: i32, y2: i32) -> Self {
203        Self(Rect::new(x1, y1, x2 - 1, y2 - 1))
204    }
205
206    /// Create a new selection from a rectangle.
207    pub fn from(r: Rect<i32>) -> Self {
208        Self::new(r.x1, r.y1, r.x2, r.y2)
209    }
210
211    /// Return the selection bounds as a non-empty rectangle. This function
212    /// will never return an empty rectangle.
213    pub fn bounds(&self) -> Rect<i32> {
214        Rect::new(self.x1, self.y1, self.x2 + 1, self.y2 + 1)
215    }
216
217    /// Return the absolute selection.
218    pub fn abs(&self) -> Selection {
219        Self(self.0.abs())
220    }
221
222    /// Translate the selection rectangle.
223    pub fn translate(&mut self, x: i32, y: i32) {
224        self.0 += Vector2::new(x, y)
225    }
226
227    /// Resize the selection by a certain amount.
228    pub fn resize(&mut self, x: i32, y: i32) {
229        self.0.x2 += x;
230        self.0.y2 += y;
231    }
232}
233
234impl Deref for Selection {
235    type Target = Rect<i32>;
236
237    fn deref(&self) -> &Rect<i32> {
238        &self.0
239    }
240}
241
242/// Session effects. Eg. view creation/destruction.
243/// Anything the renderer might want to know.
244#[derive(Clone, Debug)]
245pub enum Effect {
246    /// When the session has been resized.
247    SessionResized(LogicalSize),
248    /// When a view has been activated.
249    ViewActivated(ViewId),
250    /// When a view has been added.
251    ViewAdded(ViewId),
252    /// When a view has been removed.
253    ViewRemoved(ViewId),
254    /// When a view has been touched (edited).
255    ViewTouched(ViewId),
256    /// When a view requires re-drawing.
257    ViewDamaged(ViewId),
258    /// When the active view is non-permanently painted on.
259    ViewPaintDraft(Vec<Shape>),
260    /// When the active view is painted on.
261    ViewPaintFinal(Vec<Shape>),
262    /// The blend mode used for painting has changed.
263    ViewBlendingChanged(Blending),
264}
265
266#[derive(PartialEq, Eq, Clone, Debug)]
267pub enum ExitReason {
268    Normal,
269    Error(String),
270}
271
272impl Default for ExitReason {
273    fn default() -> Self {
274        Self::Normal
275    }
276}
277
278/// Session state.
279#[derive(PartialEq, Eq, Clone, Debug)]
280pub enum State {
281    /// The session is initializing.
282    Initializing,
283    /// The session is running normally.
284    Running,
285    /// The session is paused. Inputs are not processed.
286    Paused,
287    /// The session is being shut down.
288    Closing(ExitReason),
289}
290
291/// An editing tool.
292#[derive(Debug, Clone)]
293pub enum Tool {
294    /// The standard drawing tool.
295    Brush(Brush),
296    /// Used to sample colors.
297    Sampler,
298    /// Used to pan the workspace.
299    Pan(PanState),
300}
301
302impl Default for Tool {
303    fn default() -> Self {
304        Tool::Brush(Brush::default())
305    }
306}
307
308#[derive(Debug, Clone, Copy)]
309pub enum PanState {
310    Panning,
311    NotPanning,
312}
313
314impl Default for PanState {
315    fn default() -> Self {
316        Self::NotPanning
317    }
318}
319
320///////////////////////////////////////////////////////////////////////////////
321
322/// A generic direction that can be used for things that go backward
323/// and forward.
324#[derive(Copy, Clone, PartialEq, Eq, Debug)]
325pub enum Direction {
326    Backward,
327    Forward,
328}
329
330impl From<Direction> for i32 {
331    fn from(dir: Direction) -> i32 {
332        match dir {
333            Direction::Backward => -1,
334            Direction::Forward => 1,
335        }
336    }
337}
338
339/// A message to the user, displayed in the session.
340pub struct Message {
341    /// The message string.
342    string: String,
343    /// The message type.
344    message_type: MessageType,
345}
346
347impl Message {
348    /// Create a new message.
349    pub fn new<D: fmt::Display>(s: D, t: MessageType) -> Self {
350        Message {
351            string: format!("{}", s),
352            message_type: t,
353        }
354    }
355
356    /// Return the color of a message.
357    pub fn color(&self) -> Rgba8 {
358        self.message_type.color()
359    }
360
361    pub fn is_replay(&self) -> bool {
362        self.message_type == MessageType::Replay
363    }
364
365    pub fn is_debug(&self) -> bool {
366        self.message_type == MessageType::Debug
367    }
368
369    /// Log a message to stdout/stderr.
370    fn log(&self) {
371        match self.message_type {
372            MessageType::Info => info!("{}", self),
373            MessageType::Hint => {}
374            MessageType::Echo => info!("{}", self),
375            MessageType::Error => error!("{}", self),
376            MessageType::Warning => warn!("{}", self),
377            MessageType::Replay => {}
378            MessageType::Okay => info!("{}", self),
379            MessageType::Debug => debug!("{}", self),
380        }
381    }
382}
383
384impl Default for Message {
385    fn default() -> Self {
386        Message::new("", MessageType::Info)
387    }
388}
389
390impl std::fmt::Display for Message {
391    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
392        self.string.fmt(f)
393    }
394}
395
396/// The type of a `Message`.
397#[derive(Eq, PartialEq, Clone, Copy, Debug)]
398pub enum MessageType {
399    /// A hint that can be ignored.
400    Hint,
401    /// Informational message.
402    Info,
403    /// A message that is displayed by the `:echo` command.
404    Echo,
405    /// An error message.
406    Error,
407    /// Non-critical warning.
408    Warning,
409    /// Replay-related message.
410    Replay,
411    /// Debug message.
412    Debug,
413    /// Success message.
414    Okay,
415}
416
417impl MessageType {
418    /// Returns the color associated with a `MessageType`.
419    fn color(self) -> Rgba8 {
420        match self {
421            MessageType::Info => color::LIGHT_GREY,
422            MessageType::Hint => color::DARK_GREY,
423            MessageType::Echo => color::LIGHT_GREEN,
424            MessageType::Error => color::RED,
425            MessageType::Warning => color::YELLOW,
426            MessageType::Replay => color::GREY,
427            MessageType::Debug => color::LIGHT_GREEN,
428            MessageType::Okay => color::GREEN,
429        }
430    }
431}
432
433///////////////////////////////////////////////////////////////////////////////
434
435/// A session error.
436type Error = String;
437
438/// A key binding.
439#[derive(Clone, Debug)]
440pub struct KeyBinding {
441    /// The `Mode`s this binding applies to.
442    pub modes: Vec<Mode>,
443    /// Modifiers which must be held.
444    pub modifiers: ModifiersState,
445    /// Key which must be pressed or released.
446    pub key: Key,
447    /// Whether the key should be pressed or released.
448    pub state: InputState,
449    /// The `Command` to run when this binding is triggered.
450    pub command: Command,
451    /// Whether this key binding controls a toggle.
452    pub is_toggle: bool,
453    /// How this key binding should be displayed to the user.
454    /// If `None`, then this binding shouldn't be shown to the user.
455    pub display: Option<String>,
456}
457
458/// Manages a list of key bindings.
459#[derive(Debug)]
460pub struct KeyBindings {
461    elems: Vec<KeyBinding>,
462}
463
464impl Default for KeyBindings {
465    fn default() -> Self {
466        // The only defaults are switching to command mode and 'help'. On some platforms,
467        // Pressing `<shift> + ;` sends us a `:` directly, while on others
468        // we get `<shift>` and `;`.
469        KeyBindings {
470            elems: vec![
471                KeyBinding {
472                    modes: vec![Mode::Normal, Mode::Help],
473                    modifiers: ModifiersState {
474                        shift: true,
475                        ctrl: false,
476                        alt: false,
477                        meta: false,
478                    },
479                    key: Key::Virtual(platform::Key::Slash),
480                    state: InputState::Pressed,
481                    command: Command::Mode(Mode::Help),
482                    is_toggle: false,
483                    display: Some("?".to_string()),
484                },
485                KeyBinding {
486                    modes: vec![
487                        Mode::Normal,
488                        Mode::Visual(VisualState::Pasting),
489                        Mode::Visual(VisualState::selecting()),
490                    ],
491                    modifiers: ModifiersState {
492                        shift: true,
493                        ctrl: false,
494                        alt: false,
495                        meta: false,
496                    },
497                    key: Key::Virtual(platform::Key::Semicolon),
498                    state: InputState::Pressed,
499                    command: Command::Mode(Mode::Command),
500                    is_toggle: false,
501                    display: Some(":".to_string()),
502                },
503                #[cfg(winit)]
504                KeyBinding {
505                    modes: vec![Mode::Normal],
506                    modifiers: ModifiersState {
507                        shift: true,
508                        ctrl: false,
509                        alt: false,
510                        meta: false,
511                    },
512                    key: Key::Virtual(platform::Key::Colon),
513                    state: InputState::Pressed,
514                    command: Command::Mode(Mode::Command),
515                    is_toggle: false,
516                    display: None,
517                },
518            ],
519        }
520    }
521}
522
523impl KeyBindings {
524    /// Add a key binding.
525    pub fn add(&mut self, binding: KeyBinding) {
526        self.elems.push(binding);
527    }
528
529    /// Find a key binding based on some input state.
530    pub fn find(
531        &self,
532        key: Key,
533        modifiers: ModifiersState,
534        state: InputState,
535        mode: Mode,
536    ) -> Option<KeyBinding> {
537        self.elems.iter().rev().cloned().find(|kb| {
538            kb.key == key
539                && kb.state == state
540                && kb.modes.contains(&mode)
541                && (kb.modifiers == modifiers || state == InputState::Released)
542        })
543    }
544
545    /// Iterate over all key bindings.
546    pub fn iter(&self) -> std::slice::Iter<'_, KeyBinding> {
547        self.elems.iter()
548    }
549}
550
551///////////////////////////////////////////////////////////////////////////////
552
553/// A dictionary used to store session settings.
554#[derive(Debug)]
555pub struct Settings {
556    map: HashMap<String, Value>,
557}
558
559impl Settings {
560    const DEPRECATED: &'static [&'static str] = &["frame_delay"];
561
562    /// Presentation mode.
563    pub fn present_mode(&self) -> PresentMode {
564        if self["vsync"].is_set() {
565            PresentMode::Vsync
566        } else {
567            PresentMode::NoVsync
568        }
569    }
570
571    /// Lookup a setting.
572    pub fn get(&self, setting: &str) -> Option<&Value> {
573        self.map.get(setting)
574    }
575
576    /// Set an existing setting to a new value. Returns `Err` if there is a type
577    /// mismatch or the setting isn't found. Otherwise, returns `Ok` with the
578    /// old value.
579    pub fn set(&mut self, k: &str, v: Value) -> Result<Value, Error> {
580        if let Some(current) = self.get(k) {
581            if std::mem::discriminant(&v) == std::mem::discriminant(current) {
582                return Ok(self.map.insert(k.to_string(), v).unwrap());
583            }
584            Err(format!(
585                "invalid value `{}`, expected {}",
586                v,
587                current.description()
588            ))
589        } else {
590            Err(format!("no such setting `{}`", k))
591        }
592    }
593}
594
595impl Default for Settings {
596    /// The default settings.
597    fn default() -> Self {
598        Self {
599            map: hashmap! {
600                "debug" => Value::Bool(false),
601                "checker" => Value::Bool(false),
602                "background" => Value::Rgba8(color::BLACK),
603                "vsync" => Value::Bool(false),
604                "input/delay" => Value::F32(8.0),
605                "input/mouse" => Value::Bool(true),
606                "scale" => Value::F32(1.0),
607                "animation" => Value::Bool(true),
608                "animation/delay" => Value::U32(160),
609                "ui/palette" => Value::Bool(true),
610                "ui/status" => Value::Bool(true),
611                "ui/cursor" => Value::Bool(true),
612                "ui/message" => Value::Bool(true),
613                "ui/switcher" => Value::Bool(true),
614                "ui/view-info" => Value::Bool(true),
615
616                "grid" => Value::Bool(false),
617                "grid/color" => Value::Rgba8(color::BLUE),
618                "grid/spacing" => Value::U32Tuple(8, 8),
619
620                // Deprecated.
621                "frame_delay" => Value::F32(0.0)
622            },
623        }
624    }
625}
626
627impl std::ops::Index<&str> for Settings {
628    type Output = Value;
629
630    fn index(&self, setting: &str) -> &Self::Output {
631        &self
632            .get(setting)
633            .expect(&format!("setting {} should exist", setting))
634    }
635}
636
637///////////////////////////////////////////////////////////////////////////////
638
639/// The user session.
640///
641/// Stores all relevant session state.
642pub struct Session {
643    /// The current session `Mode`.
644    pub mode: Mode,
645    /// The previous `Mode`.
646    pub prev_mode: Option<Mode>,
647    /// The current session `State`.
648    pub state: State,
649
650    /// The width of the session workspace.
651    pub width: f32,
652    /// The height of the session workspace.
653    pub height: f32,
654
655    /// The HiDPI factor of the host.
656    pub hidpi_factor: f64,
657
658    /// The cursor coordinates.
659    pub cursor: SessionCoords,
660
661    /// The color under the cursor, if any.
662    pub hover_color: Option<Rgba8>,
663    /// The view under the cursor, if any.
664    pub hover_view: Option<ViewId>,
665
666    /// The workspace offset. Views are offset by this vector.
667    pub offset: Vector2<f32>,
668    /// The current message displayed to the user.
669    pub message: Message,
670
671    /// The session foreground color.
672    pub fg: Rgba8,
673    /// The session background color.
674    pub bg: Rgba8,
675
676    /// The current frame number.
677    frame_number: u64,
678
679    /// Directories in which user configuration is stored.
680    base_dirs: dirs::ProjectDirs,
681
682    /// Resources shared with the `Renderer`.
683    resources: ResourceManager,
684
685    /// Whether we should ignore characters received.
686    ignore_received_characters: bool,
687    /// The set of keys currently pressed.
688    keys_pressed: HashSet<platform::Key>,
689    /// The list of all active key bindings.
690    pub key_bindings: KeyBindings,
691
692    /// Current pixel selection.
693    pub selection: Option<Selection>,
694
695    /// The session's current settings.
696    pub settings: Settings,
697    /// Settings recently changed.
698    pub settings_changed: HashSet<String>,
699
700    /// Views loaded in the session.
701    pub views: ViewManager,
702    /// Effects produced by the session. Cleared at the beginning of every
703    /// update.
704    pub effects: Vec<Effect>,
705
706    /// The current state of the command line.
707    pub cmdline: CommandLine,
708    /// The color palette.
709    pub palette: Palette,
710
711    /// Average time it takes for a session update.
712    pub avg_time: time::Duration,
713
714    /// The current tool. Only used in `Normal` mode.
715    pub tool: Tool,
716    /// The previous tool, if any.
717    pub prev_tool: Option<Tool>,
718
719    /// Input state of the mouse.
720    mouse_state: InputState,
721
722    /// Internal command bus. Used to send internal messages asynchronously.
723    /// We do this when we want the renderer to have a chance to run before
724    /// the command is processed. For example, when displaying a message before
725    /// an expensive process is kicked off.
726    queue: Vec<InternalCommand>,
727}
728
729impl Session {
730    /// Maximum number of views in a session.
731    pub const MAX_VIEWS: usize = 64;
732    /// Default view width.
733    pub const DEFAULT_VIEW_W: u32 = 128;
734    /// Default view height.
735    pub const DEFAULT_VIEW_H: u32 = 128;
736
737    /// Supported image formats for writing.
738    const SUPPORTED_FORMATS: &'static [&'static str] = &["png", "gif", "svg"];
739    /// Minimum margin between views, in pixels.
740    const VIEW_MARGIN: f32 = 24.;
741    /// Size of palette cells, in pixels.
742    const PALETTE_CELL_SIZE: f32 = 24.;
743    /// Distance to pan when using keyboard.
744    const PAN_PIXELS: i32 = 32;
745    /// Minimum brush size.
746    const MIN_BRUSH_SIZE: usize = 1;
747    /// Maximum frame width or height.
748    const MAX_FRAME_SIZE: u32 = 4096;
749    /// Maximum zoom amount as a multiplier.
750    const MAX_ZOOM: f32 = 128.0;
751    /// Zoom levels used when zooming in/out.
752    const ZOOM_LEVELS: &'static [f32] = &[
753        1.,
754        2.,
755        3.,
756        4.,
757        6.,
758        8.,
759        10.,
760        12.,
761        16.,
762        20.,
763        24.,
764        32.,
765        64.,
766        Self::MAX_ZOOM,
767    ];
768
769    /// Name of rx initialization script.
770    const INIT: &'static str = "init.rx";
771
772    /// Create a new un-initialized session.
773    pub fn new(
774        w: u32,
775        h: u32,
776        hidpi_factor: f64,
777        resources: ResourceManager,
778        base_dirs: dirs::ProjectDirs,
779    ) -> Self {
780        Self {
781            state: State::Initializing,
782            width: w as f32,
783            height: h as f32,
784            hidpi_factor,
785            cursor: SessionCoords::new(0., 0.),
786            base_dirs,
787            offset: Vector2::zero(),
788            tool: Tool::default(),
789            prev_tool: Option::default(),
790            mouse_state: InputState::Released,
791            hover_color: Option::default(),
792            hover_view: Option::default(),
793            fg: color::WHITE,
794            bg: color::BLACK,
795            settings: Settings::default(),
796            settings_changed: HashSet::new(),
797            views: ViewManager::new(),
798            effects: Vec::new(),
799            palette: Palette::new(Self::PALETTE_CELL_SIZE),
800            key_bindings: KeyBindings::default(),
801            keys_pressed: HashSet::new(),
802            ignore_received_characters: false,
803            cmdline: CommandLine::new(),
804            mode: Mode::Normal,
805            prev_mode: Option::default(),
806            selection: Option::default(),
807            message: Message::default(),
808            resources,
809            avg_time: time::Duration::from_secs(0),
810            frame_number: 0,
811            queue: Vec::new(),
812        }
813    }
814
815    /// Initialize a session.
816    pub fn init(mut self, source: Option<PathBuf>) -> std::io::Result<Self> {
817        self.transition(State::Running);
818        self.reset()?;
819
820        let cwd = std::env::current_dir()?;
821
822        if let Some(init) = source {
823            // The special source '-' is used to skip initialization.
824            if init.as_os_str() != "-" {
825                self.source_path(&init)?;
826            }
827        } else {
828            let dir = self.base_dirs.config_dir().to_owned();
829            let cfg = dir.join(Self::INIT);
830
831            if cfg.exists() {
832                self.source_path(cfg)?;
833            }
834        }
835        self.source_dir(cwd).ok();
836        self.message(format!("rx v{}", crate::VERSION), MessageType::Debug);
837
838        Ok(self)
839    }
840
841    // Reset to factory defaults.
842    pub fn reset(&mut self) -> io::Result<()> {
843        self.key_bindings = KeyBindings::default();
844        self.settings = Settings::default();
845        self.tool = Tool::default();
846
847        self.source_reader(io::BufReader::new(data::CONFIG), "<init>")
848    }
849
850    /// Create a blank view.
851    pub fn blank(&mut self, fs: FileStatus, w: u32, h: u32) {
852        let id = self.views.add(fs, w, h);
853
854        self.effects.push(Effect::ViewAdded(id));
855        self.resources.add_blank_view(id, w, h);
856        self.organize_views();
857        self.edit_view(id);
858    }
859
860    /// Transition to a new state. Only allows valid state transitions.
861    pub fn transition(&mut self, to: State) {
862        match (&self.state, &to) {
863            (State::Initializing, State::Running)
864            | (State::Running, State::Paused)
865            | (State::Paused, State::Running)
866            | (State::Paused, State::Closing(_))
867            | (State::Running, State::Closing(_)) => {
868                debug!("state: {:?} -> {:?}", self.state, to);
869                self.state = to;
870            }
871            _ => {}
872        }
873    }
874
875    /// Update the session by processing new user events and advancing
876    /// the internal state.
877    pub fn update(
878        &mut self,
879        events: &mut Vec<Event>,
880        exec: Rc<RefCell<Execution>>,
881        delta: time::Duration,
882        avg_time: time::Duration,
883    ) -> Vec<Effect> {
884        self.settings_changed.clear();
885        self.avg_time = avg_time;
886
887        if let Tool::Brush(ref mut b) = self.tool {
888            b.update();
889        }
890
891        for (_, v) in self.views.iter_mut() {
892            v.okay();
893
894            if self.settings["animation"].is_set() {
895                v.update(delta);
896            }
897        }
898
899        let exec = &mut *exec.borrow_mut();
900
901        // TODO: This whole block needs refactoring..
902        if let Execution::Replaying {
903            events: recording,
904            digest: DigestState { mode, .. },
905            result,
906            ..
907        } = exec
908        {
909            let mode = *mode;
910            let result = result.clone();
911
912            {
913                let frame = self.frame_number;
914                let end = recording.iter().position(|t| t.frame != frame);
915
916                recording
917                    .drain(..end.unwrap_or_else(|| recording.len()))
918                    .collect::<Vec<TimedEvent>>()
919                    .into_iter()
920                    .for_each(|t| self.handle_event(t.event, exec));
921
922                let verify_ended = mode == DigestMode::Verify && result.is_done() && end.is_none();
923                let replay_ended = mode != DigestMode::Verify && end.is_none();
924                let verify_failed = result.is_err();
925
926                // Replay is over.
927                if verify_ended || replay_ended || verify_failed {
928                    self.release_inputs();
929                    self.message("Replay ended", MessageType::Replay);
930
931                    match mode {
932                        DigestMode::Verify => {
933                            if result.is_ok() {
934                                info!("replaying: {}", result.summary());
935                                self.quit(ExitReason::Normal);
936                            } else {
937                                self.quit(ExitReason::Error(format!(
938                                    "replay failed: {}",
939                                    result.summary()
940                                )));
941                            }
942                        }
943                        DigestMode::Record => match exec.finalize_replaying() {
944                            Ok(path) => {
945                                info!("replaying: digest saved to `{}`", path.display());
946                            }
947                            Err(e) => {
948                                error!("replaying: error saving recording: {}", e);
949                            }
950                        },
951                        DigestMode::Ignore => {}
952                    }
953                    *exec = Execution::Normal;
954                }
955            }
956
957            for event in events.drain(..) {
958                match event {
959                    Event::KeyboardInput(platform::KeyboardInput {
960                        key: Some(platform::Key::Escape),
961                        ..
962                    }) => {
963                        self.release_inputs();
964                        self.message("Replay ended", MessageType::Replay);
965
966                        *exec = Execution::Normal;
967                    }
968                    _ => debug!("event (ignored): {:?}", event),
969                }
970            }
971        } else {
972            // A common case is that we have multiple `CursorMoved` events
973            // in one update. In that case we keep only the last one,
974            // since the in-betweens will never be seen.
975            if events.len() > 1
976                && events.iter().all(|e| match e {
977                    Event::CursorMoved(_) => true,
978                    _ => false,
979                })
980            {
981                events.drain(..events.len() - 1);
982            }
983
984            let cmds: Vec<_> = self.queue.drain(..).collect();
985            for cmd in cmds.into_iter() {
986                self.handle_internal_cmd(cmd, exec);
987            }
988
989            for event in events.drain(..) {
990                self.handle_event(event, exec);
991            }
992        }
993
994        if let Tool::Brush(ref brush) = self.tool {
995            let output = brush.output(
996                Stroke::NONE,
997                Fill::Solid(brush.color.into()),
998                1.0,
999                Origin::BottomLeft,
1000            );
1001            if !output.is_empty() {
1002                match brush.state {
1003                    // If we're erasing, we can't use the staging framebuffer, since we
1004                    // need to be replacing pixels on the real buffer.
1005                    _ if brush.is_set(BrushMode::Erase) => {
1006                        self.effects.extend_from_slice(&[
1007                            Effect::ViewBlendingChanged(Blending::constant()),
1008                            Effect::ViewPaintFinal(output),
1009                        ]);
1010                    }
1011                    // As long as we haven't finished drawing, render into the staging buffer.
1012                    BrushState::DrawStarted(_) | BrushState::Drawing(_) => {
1013                        self.effects.push(Effect::ViewPaintDraft(output));
1014                    }
1015                    // Once we're done drawing, we can render into the real buffer.
1016                    BrushState::DrawEnded(_) => {
1017                        self.effects.extend_from_slice(&[
1018                            Effect::ViewBlendingChanged(Blending::default()),
1019                            Effect::ViewPaintFinal(output),
1020                        ]);
1021                    }
1022                    // If the brush output isn't empty, we can't possibly not
1023                    // be drawing!
1024                    BrushState::NotDrawing => unreachable!(),
1025                }
1026            }
1027        }
1028
1029        if self.views.is_empty() {
1030            self.quit(ExitReason::Normal);
1031        } else {
1032            for (id, v) in self.views.iter() {
1033                if v.is_dirty() {
1034                    self.effects.push(Effect::ViewTouched(*id));
1035                } else if v.is_damaged() {
1036                    self.effects.push(Effect::ViewDamaged(*id));
1037                }
1038            }
1039        }
1040
1041        match exec {
1042            Execution::Replaying {
1043                events: recording,
1044                digest: DigestState { mode, .. },
1045                ..
1046            } if *mode == DigestMode::Verify || *mode == DigestMode::Record => {
1047                // Skip to the next event frame to speed up replay.
1048                self.frame_number = recording
1049                    .front()
1050                    .map(|e| e.frame)
1051                    .unwrap_or(self.frame_number + 1);
1052            }
1053            _ => {
1054                self.frame_number += 1;
1055            }
1056        }
1057
1058        // Make sure we don't have rounding errors
1059        debug_assert_eq!(self.offset, self.offset.map(|a| a.floor()));
1060
1061        // Return and drain accumulated effects
1062        self.effects()
1063    }
1064
1065    /// Quit the session.
1066    pub fn quit(&mut self, r: ExitReason) {
1067        self.transition(State::Closing(r));
1068    }
1069
1070    /// Drain and return effects.
1071    pub fn effects(&mut self) -> Vec<Effect> {
1072        self.effects.drain(..).collect()
1073    }
1074
1075    /// Return the session offset as a transformation matrix.
1076    pub fn transform(&self) -> Matrix4<f32> {
1077        Matrix4::from_translation(self.offset.extend(0.))
1078    }
1079
1080    /// Snap the given session coordinates to the pixel grid.
1081    /// This only has an effect at zoom levels greater than `1.0`.
1082    #[allow(dead_code)]
1083    pub fn snap(&self, p: SessionCoords, offx: f32, offy: f32, zoom: f32) -> SessionCoords {
1084        SessionCoords::new(
1085            p.x - ((p.x - offx - self.offset.x) % zoom),
1086            p.y - ((p.y - offy - self.offset.y) % zoom),
1087        )
1088        .floor()
1089    }
1090
1091    ////////////////////////////////////////////////////////////////////////////
1092
1093    /// Pan the view by a relative amount.
1094    fn pan(&mut self, x: f32, y: f32) {
1095        self.offset.x += x;
1096        self.offset.y += y;
1097
1098        self.cursor_dirty();
1099    }
1100
1101    /// Re-compute state related to the cursor position. This is useful
1102    /// when the cursor hasn't moved relative to the session, but things
1103    /// within the session have moved relative to the cursor.
1104    fn cursor_dirty(&mut self) {
1105        if !self.settings["input/mouse"].is_set() {
1106            return;
1107        }
1108        let cursor = self.cursor;
1109        let palette_hover = self.palette.hover.is_some();
1110
1111        self.palette.handle_cursor_moved(cursor);
1112        self.hover_view = None;
1113
1114        match &self.tool {
1115            Tool::Brush(b) if !b.is_drawing() => {
1116                if !palette_hover && self.palette.hover.is_some() {
1117                    // Gained palette focus with brush.
1118                    self.tool(Tool::Sampler);
1119                }
1120            }
1121            Tool::Sampler if palette_hover && self.palette.hover.is_none() => {
1122                // Lost palette focus with color sampler.
1123                self.prev_tool();
1124            }
1125            _ => {}
1126        }
1127
1128        for (_, v) in self.views.iter_mut() {
1129            if v.contains(cursor - self.offset) {
1130                self.hover_view = Some(v.id);
1131                break;
1132            }
1133        }
1134
1135        self.hover_color = if self.palette.hover.is_some() {
1136            self.palette.hover
1137        } else if let Some(v) = self.hover_view {
1138            let p: ViewCoords<u32> = self.view_coords(v, cursor).into();
1139            self.color_at(v, p)
1140        } else {
1141            None
1142        };
1143    }
1144
1145    /// Called when settings have been changed.
1146    fn setting_changed(&mut self, name: &str, old: &Value, new: &Value) {
1147        debug!("set `{}`: {} -> {}", name, old, new);
1148
1149        self.settings_changed.insert(name.to_owned());
1150
1151        match name {
1152            "animation/delay" => {
1153                self.active_view_mut().set_animation_delay(new.uint64());
1154            }
1155            "scale" => {
1156                // TODO: We need to recompute the cursor position here
1157                // from the window coordinates. Currently, cursor position
1158                // is stored only in `SessionCoords`, which would have
1159                // to change.
1160            }
1161            _ => {}
1162        }
1163    }
1164
1165    /// Toggle the session mode.
1166    fn toggle_mode(&mut self, mode: Mode) {
1167        if self.mode == mode {
1168            self.switch_mode(Mode::Normal);
1169        } else {
1170            self.switch_mode(mode);
1171        }
1172    }
1173
1174    /// Switch the session mode.
1175    fn switch_mode(&mut self, mode: Mode) {
1176        let (old, new) = (self.mode, mode);
1177        if old == new {
1178            return;
1179        }
1180
1181        match old {
1182            Mode::Command => {
1183                self.cmdline.clear();
1184            }
1185            _ => {}
1186        }
1187
1188        match new {
1189            Mode::Normal => {
1190                self.selection = None;
1191            }
1192            Mode::Command => {
1193                // When switching to command mode via the keyboard, we simultaneously
1194                // also receive the character input equivalent of the key pressed.
1195                // This input, since we are now in command mode, is processed as
1196                // text input to the command line. To avoid this, we have to ignore
1197                // all such input until the end of the current upate.
1198                self.ignore_received_characters = true;
1199                self.cmdline_handle_input(':');
1200            }
1201            _ => {}
1202        }
1203
1204        self.release_inputs();
1205        self.prev_mode = Some(self.mode);
1206        self.mode = new;
1207    }
1208
1209    /// Release all keys and mouse buttons.
1210    fn release_inputs(&mut self) {
1211        let pressed: Vec<platform::Key> = self.keys_pressed.iter().cloned().collect();
1212        for k in pressed {
1213            self.handle_keyboard_input(
1214                platform::KeyboardInput {
1215                    key: Some(k),
1216                    modifiers: ModifiersState::default(),
1217                    state: InputState::Released,
1218                },
1219                &mut Execution::Normal,
1220            );
1221        }
1222        if self.mouse_state == InputState::Pressed {
1223            self.handle_mouse_input(platform::MouseButton::Left, InputState::Released);
1224        }
1225    }
1226
1227    ///////////////////////////////////////////////////////////////////////////////
1228    /// Messages
1229    ///////////////////////////////////////////////////////////////////////////////
1230
1231    /// Display a message to the user. Also logs.
1232    pub fn message<D: fmt::Display>(&mut self, msg: D, t: MessageType) {
1233        self.message = Message::new(msg, t);
1234        self.message.log();
1235    }
1236
1237    fn message_clear(&mut self) {
1238        self.message = Message::default();
1239    }
1240
1241    fn unimplemented(&mut self) {
1242        self.message("Error: not yet implemented", MessageType::Error);
1243    }
1244
1245    ///////////////////////////////////////////////////////////////////////////////
1246    /// View functions
1247    ///////////////////////////////////////////////////////////////////////////////
1248
1249    /// Get the view with the given id.
1250    ///
1251    /// # Panics
1252    ///
1253    /// Panics if the view isn't found.
1254    pub fn view(&self, id: ViewId) -> &View {
1255        self.views
1256            .get(&id)
1257            .expect(&format!("view #{} must exist", id))
1258    }
1259
1260    /// Get the view with the given id (mutable).
1261    ///
1262    /// # Panics
1263    ///
1264    /// Panics if the view isn't found.
1265    pub fn view_mut(&mut self, id: ViewId) -> &mut View {
1266        self.views
1267            .get_mut(id)
1268            .expect(&format!("view #{} must exist", id))
1269    }
1270
1271    /// Get the currently active view.
1272    ///
1273    /// # Panics
1274    ///
1275    /// Panics if there is no active view.
1276    pub fn active_view(&self) -> &View {
1277        assert!(
1278            self.views.active_id != ViewId::default(),
1279            "fatal: no active view"
1280        );
1281        self.view(self.views.active_id)
1282    }
1283
1284    /// Get the currently active view (mutable).
1285    ///
1286    /// # Panics
1287    ///
1288    /// Panics if there is no active view.
1289    pub fn active_view_mut(&mut self) -> &mut View {
1290        assert!(
1291            self.views.active_id != ViewId::default(),
1292            "fatal: no active view"
1293        );
1294        self.view_mut(self.views.active_id)
1295    }
1296
1297    /// Activate a view. This makes the given view the "active" view.
1298    pub fn activate(&mut self, id: ViewId) {
1299        if self.views.active_id == id {
1300            return;
1301        }
1302        self.views.activate(id);
1303        self.effects.push(Effect::ViewActivated(id));
1304    }
1305
1306    /// Check whether a view is active.
1307    pub fn is_active(&self, id: ViewId) -> bool {
1308        self.views.active_id == id
1309    }
1310
1311    /// Convert "logical" window coordinates to session coordinates.
1312    pub fn window_to_session_coords(&self, position: platform::LogicalPosition) -> SessionCoords {
1313        let (x, y) = (position.x, position.y);
1314        let scale: f64 = self.settings["scale"].float64();
1315        SessionCoords::new(
1316            (x / scale).floor() as f32,
1317            self.height - (y / scale).floor() as f32 - 1.,
1318        )
1319    }
1320
1321    /// Convert session coordinates to view coordinates of the given view.
1322    pub fn view_coords(&self, v: ViewId, p: SessionCoords) -> ViewCoords<f32> {
1323        let v = self.view(v);
1324        let SessionCoords(mut p) = p;
1325
1326        p = p - self.offset - v.offset;
1327        p = p / v.zoom;
1328
1329        if v.flip_x {
1330            p.x = v.width() as f32 - p.x;
1331        }
1332        if v.flip_y {
1333            p.y = v.height() as f32 - p.y;
1334        }
1335
1336        ViewCoords::new(p.x.floor(), p.y.floor())
1337    }
1338
1339    /// Convert view coordinates to session coordinates.
1340    pub fn session_coords(&self, v: ViewId, p: ViewCoords<f32>) -> SessionCoords {
1341        let v = self.view(v);
1342
1343        let p = Point2::new(p.x * v.zoom, p.y * v.zoom);
1344        let p = p + self.offset + v.offset;
1345
1346        if v.flip_x {
1347            unimplemented!();
1348        }
1349        if v.flip_y {
1350            unimplemented!();
1351        }
1352
1353        SessionCoords::new(p.x, p.y).floor()
1354    }
1355
1356    /// Convert session coordinates to view coordinates of the active view.
1357    pub fn active_view_coords(&self, p: SessionCoords) -> ViewCoords<f32> {
1358        self.view_coords(self.views.active_id, p)
1359    }
1360
1361    /// Check whether a point is inside the selection, if any.
1362    pub fn is_selected(&self, p: ViewCoords<i32>) -> bool {
1363        if let Some(s) = self.selection {
1364            s.abs().bounds().contains(*p)
1365        } else {
1366            false
1367        }
1368    }
1369
1370    /// Edit paths.
1371    ///
1372    /// Loads the given files into the session. Returns an error if one of
1373    /// the paths couldn't be loaded. If a path points to a directory,
1374    /// loads all files within that directory.
1375    ///
1376    /// If a path doesn't exist, creates a blank view for that path.
1377    pub fn edit<P: AsRef<Path>>(&mut self, paths: &[P]) -> io::Result<()> {
1378        use std::ffi::OsStr;
1379
1380        // TODO: Keep loading paths even if some fail?
1381        for path in paths {
1382            let path = path.as_ref();
1383
1384            if path.is_dir() {
1385                for entry in path.read_dir()? {
1386                    let entry = entry?;
1387                    let path = entry.path();
1388
1389                    if path.is_dir() {
1390                        continue;
1391                    }
1392                    if path.file_name() == Some(OsStr::new(".rxrc")) {
1393                        continue;
1394                    }
1395
1396                    self.load_view(path)?;
1397                }
1398                self.source_dir(path).ok();
1399            } else if path.exists() {
1400                self.load_view(path)?;
1401            } else if !path.exists() && path.with_extension("png").exists() {
1402                self.load_view(path.with_extension("png"))?;
1403            } else {
1404                let (w, h) = if !self.views.is_empty() {
1405                    let v = self.active_view();
1406                    (v.width(), v.height())
1407                } else {
1408                    (Self::DEFAULT_VIEW_W, Self::DEFAULT_VIEW_H)
1409                };
1410                self.blank(FileStatus::New(path.with_extension("png")), w, h);
1411            }
1412        }
1413
1414        if let Some(id) = self.views.keys().cloned().next_back() {
1415            self.organize_views();
1416            self.edit_view(id);
1417        }
1418
1419        Ok(())
1420    }
1421
1422    /// Save the given view to disk with the current file name. Returns
1423    /// an error if the view has no file name.
1424    pub fn save_view(&mut self, id: ViewId) -> io::Result<()> {
1425        if let Some(ref f) = self.view(id).file_name().cloned() {
1426            self.save_view_as(id, f)
1427        } else {
1428            Err(io::Error::new(io::ErrorKind::Other, "no file name given"))
1429        }
1430    }
1431
1432    /// Save a view with the given file name. Returns an error if
1433    /// the format is not supported.
1434    pub fn save_view_as<P: AsRef<Path>>(&mut self, id: ViewId, path: P) -> io::Result<()> {
1435        let ext = path.as_ref().extension().ok_or_else(|| {
1436            io::Error::new(
1437                io::ErrorKind::Other,
1438                "file path requires an extension (.gif or .png)",
1439            )
1440        })?;
1441        let ext = ext.to_str().ok_or_else(|| {
1442            io::Error::new(io::ErrorKind::Other, "file extension is not valid unicode")
1443        })?;
1444
1445        if !Self::SUPPORTED_FORMATS.contains(&ext) {
1446            return Err(io::Error::new(
1447                io::ErrorKind::InvalidInput,
1448                format!("`{}` is not a supported output format", ext),
1449            ));
1450        }
1451
1452        if ext == "gif" {
1453            return self.save_view_gif(id, path);
1454        } else if ext == "svg" {
1455            return self.save_view_svg(id, path);
1456        }
1457
1458        // Make sure we don't overwrite other files!
1459        if self
1460            .view(id)
1461            .file_name()
1462            .map_or(true, |f| path.as_ref() != f)
1463            && path.as_ref().exists()
1464        {
1465            return Err(io::Error::new(
1466                io::ErrorKind::AlreadyExists,
1467                format!("\"{}\" already exists", path.as_ref().display()),
1468            ));
1469        }
1470
1471        let (s_id, npixels) = self.resources.save_view(id, &path)?;
1472        self.view_mut(id).save_as(s_id, path.as_ref().into());
1473
1474        self.message(
1475            format!("\"{}\" {} pixels written", path.as_ref().display(), npixels,),
1476            MessageType::Info,
1477        );
1478        Ok(())
1479    }
1480
1481    /// Private ///////////////////////////////////////////////////////////////////
1482
1483    /// Load a view into the session.
1484    fn load_view<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
1485        let path = path.as_ref();
1486
1487        debug!("load: {:?}", path);
1488
1489        match path.extension() {
1490            Some(ext) if ext != "png" => {
1491                return Err(io::Error::new(
1492                    io::ErrorKind::Other,
1493                    "trying to load file with unsupported extension",
1494                ));
1495            }
1496            None => {
1497                return Err(io::Error::new(
1498                    io::ErrorKind::Other,
1499                    "trying to load file with no extension",
1500                ));
1501            }
1502            _ => {}
1503        }
1504
1505        // View is already loaded.
1506        if let Some(View { id, .. }) = self
1507            .views
1508            .values()
1509            .find(|v| v.file_name().map_or(false, |f| f == path))
1510        {
1511            // TODO: Reload from disk.
1512            let id = *id;
1513            self.activate(id);
1514            return Ok(());
1515        }
1516
1517        if let Some(v) = self.views.active() {
1518            let id = v.id;
1519
1520            if v.file_status == FileStatus::NoFile {
1521                self.destroy_view(id);
1522            }
1523        }
1524
1525        let (width, height, pixels) = ResourceManager::load_image(&path)?;
1526        let id = self
1527            .views
1528            .add(FileStatus::Saved(path.into()), width as u32, height as u32);
1529
1530        self.effects.push(Effect::ViewAdded(id));
1531        self.resources.add_view(id, width, height, &pixels);
1532        self.message(
1533            format!("\"{}\" {} pixels read", path.display(), width * height),
1534            MessageType::Info,
1535        );
1536
1537        Ok(())
1538    }
1539
1540    /// Destroys the resources associated with a view.
1541    fn destroy_view(&mut self, id: ViewId) {
1542        assert!(!self.views.is_empty());
1543
1544        self.views.remove(id);
1545        self.resources.remove_view(id);
1546        self.effects.push(Effect::ViewRemoved(id));
1547    }
1548
1549    /// Quit the view.
1550    fn quit_view(&mut self, id: ViewId) {
1551        self.destroy_view(id);
1552
1553        if !self.views.is_empty() {
1554            self.organize_views();
1555            self.center_active_view();
1556        }
1557    }
1558
1559    /// Quit view if it has been saved. Otherwise, display an error.
1560    fn quit_view_safe(&mut self, id: ViewId) {
1561        let v = self.view(id);
1562        match &v.file_status {
1563            FileStatus::Modified(_) | FileStatus::New(_) => {
1564                self.message(
1565                    "Error: no write since last change (enter `:q!` to quit without saving)",
1566                    MessageType::Error,
1567                );
1568            }
1569            _ => self.quit_view(id),
1570        }
1571    }
1572
1573    /// Save a view as a gif animation.
1574    fn save_view_gif<P: AsRef<Path>>(&mut self, id: ViewId, path: P) -> io::Result<()> {
1575        let delay = self.view(id).animation.delay;
1576        let npixels = self
1577            .resources
1578            .save_view_gif(id, &path, delay, &self.palette.colors)?;
1579
1580        self.message(
1581            format!("\"{}\" {} pixels written", path.as_ref().display(), npixels),
1582            MessageType::Info,
1583        );
1584        Ok(())
1585    }
1586
1587    /// Save a view as an svg.
1588    fn save_view_svg<P: AsRef<Path>>(&mut self, id: ViewId, path: P) -> io::Result<()> {
1589        let npixels = self.resources.save_view_svg(id, &path)?;
1590
1591        self.message(
1592            format!("\"{}\" {} pixels written", path.as_ref().display(), npixels),
1593            MessageType::Info,
1594        );
1595        Ok(())
1596    }
1597
1598    /// Start editing the given view.
1599    fn edit_view(&mut self, id: ViewId) {
1600        self.activate(id);
1601        self.center_active_view();
1602    }
1603
1604    /// Re-position all views relative to each other so that they don't overlap.
1605    fn organize_views(&mut self) {
1606        if self.views.is_empty() {
1607            return;
1608        }
1609        let (_, first) = self
1610            .views
1611            .iter_mut()
1612            .next()
1613            .expect("view list should never be empty");
1614
1615        first.offset.y = 0.;
1616
1617        let mut offset = first.height() as f32 * first.zoom + Self::VIEW_MARGIN;
1618
1619        for (_, v) in self.views.iter_mut().skip(1) {
1620            v.offset.y = offset;
1621            offset += v.height() as f32 * v.zoom + Self::VIEW_MARGIN;
1622        }
1623        self.cursor_dirty();
1624    }
1625
1626    /// Check the current selection and invalidate it if necessary.
1627    fn check_selection(&mut self) {
1628        let v = self.active_view();
1629        let r = v.bounds();
1630        if let Some(s) = &self.selection {
1631            if !r.contains(s.min()) && !r.contains(s.max()) {
1632                self.selection = None;
1633            }
1634        }
1635    }
1636
1637    /// Yank the selection.
1638    fn yank_selection(&mut self) -> Option<Rect<i32>> {
1639        if let (Mode::Visual(VisualState::Selecting { .. }), Some(s)) = (self.mode, self.selection)
1640        {
1641            let v = self.active_view_mut();
1642            let s = s.abs().bounds();
1643
1644            if s.intersects(v.bounds()) {
1645                let s = s.intersection(v.bounds());
1646
1647                v.yank(s);
1648
1649                self.selection = Some(Selection::from(s));
1650                self.switch_mode(Mode::Visual(VisualState::Pasting));
1651
1652                return Some(s);
1653            }
1654        }
1655        None
1656    }
1657
1658    fn undo(&mut self, id: ViewId) {
1659        self.restore_view_snapshot(id, Direction::Backward);
1660    }
1661
1662    fn redo(&mut self, id: ViewId) {
1663        self.restore_view_snapshot(id, Direction::Forward);
1664    }
1665
1666    fn restore_view_snapshot(&mut self, id: ViewId, dir: Direction) {
1667        let snapshot = self
1668            .resources
1669            .lock_mut()
1670            .get_view_mut(id)
1671            .and_then(|s| {
1672                if dir == Direction::Backward {
1673                    s.prev_snapshot()
1674                } else {
1675                    s.next_snapshot()
1676                }
1677            })
1678            .map(|s| (s.id, s.extent));
1679
1680        if let Some((sid, extent)) = snapshot {
1681            let v = self.view_mut(id);
1682
1683            v.reset(extent);
1684            v.damaged();
1685
1686            // If the snapshot was saved to disk, we mark the view as saved too.
1687            // Otherwise, if the view was saved before restoring the snapshot,
1688            // we mark it as modified.
1689            match v.file_status {
1690                FileStatus::Modified(ref f) if v.is_snapshot_saved(sid) => {
1691                    v.file_status = FileStatus::Saved(f.clone());
1692                }
1693                FileStatus::Saved(ref f) => {
1694                    v.file_status = FileStatus::Modified(f.clone());
1695                }
1696                _ => {
1697                    // TODO
1698                }
1699            }
1700            self.cursor_dirty();
1701        }
1702    }
1703
1704    ///////////////////////////////////////////////////////////////////////////
1705    // Internal command handler
1706    ///////////////////////////////////////////////////////////////////////////
1707
1708    fn handle_internal_cmd(&mut self, cmd: InternalCommand, exec: &mut Execution) {
1709        match cmd {
1710            InternalCommand::StopRecording => match exec.stop_recording() {
1711                Ok(path) => {
1712                    self.message(
1713                        format!("Recording saved to `{}`", path.display()),
1714                        MessageType::Replay,
1715                    );
1716                    info!("recording: events saved to `{}`", path.display());
1717                    self.quit(ExitReason::Normal);
1718                }
1719                Err(e) => {
1720                    error!("recording: error stopping: {}", e);
1721                }
1722            },
1723        }
1724    }
1725
1726    ///////////////////////////////////////////////////////////////////////////
1727    // Event handlers
1728    ///////////////////////////////////////////////////////////////////////////
1729
1730    pub fn handle_event(&mut self, event: Event, exec: &mut Execution) {
1731        if let Execution::Recording {
1732            ref mut events,
1733            start,
1734            ..
1735        } = exec
1736        {
1737            events.push(TimedEvent::new(
1738                self.frame_number,
1739                start.elapsed(),
1740                event.clone(),
1741            ));
1742        }
1743
1744        match event {
1745            Event::MouseInput(btn, st) => {
1746                if self.settings["input/mouse"].is_set() {
1747                    self.handle_mouse_input(btn, st);
1748                }
1749            }
1750            Event::MouseWheel(delta) => {
1751                if self.settings["input/mouse"].is_set() {
1752                    self.handle_mouse_wheel(delta);
1753                }
1754            }
1755            Event::CursorMoved(position) => {
1756                if self.settings["input/mouse"].is_set() {
1757                    let coords = self.window_to_session_coords(position);
1758                    self.handle_cursor_moved(coords);
1759                }
1760            }
1761            Event::KeyboardInput(input) => self.handle_keyboard_input(input, exec),
1762            Event::ReceivedCharacter(c) => self.handle_received_character(c),
1763        }
1764    }
1765
1766    pub fn handle_resized(&mut self, size: platform::LogicalSize) {
1767        self.width = size.width as f32;
1768        self.height = size.height as f32;
1769
1770        // TODO: Reset session cursor coordinates
1771        self.center_palette();
1772        self.center_active_view();
1773
1774        self.effects.push(Effect::SessionResized(size));
1775    }
1776
1777    fn handle_mouse_input(&mut self, button: platform::MouseButton, state: platform::InputState) {
1778        if button != platform::MouseButton::Left {
1779            return;
1780        }
1781        self.mouse_state = state;
1782
1783        // Pan tool.
1784        match &mut self.tool {
1785            Tool::Pan(ref mut p) => match (&p, state) {
1786                (PanState::Panning, InputState::Released) => {
1787                    *p = PanState::NotPanning;
1788                    return;
1789                }
1790                (PanState::NotPanning, InputState::Pressed) => {
1791                    *p = PanState::Panning;
1792                    return;
1793                }
1794                _ => {}
1795            },
1796            _ => {}
1797        }
1798
1799        match state {
1800            InputState::Pressed => {
1801                // Click on palette.
1802                if let Some(color) = self.palette.hover {
1803                    if self.mode == Mode::Command {
1804                        self.cmdline.puts(&Rgb8::from(color).to_string());
1805                    } else {
1806                        self.pick_color(color);
1807                    }
1808                    return;
1809                }
1810
1811                // Click on a view.
1812                if let Some(id) = self.hover_view {
1813                    // Clicking on a view is one way to get out of command mode.
1814                    if self.mode == Mode::Command {
1815                        self.cmdline_hide();
1816                        return;
1817                    }
1818                    if self.is_active(id) {
1819                        let v = self.active_view();
1820                        let p = self.view_coords(v.id, self.cursor);
1821                        let extent = v.extent();
1822
1823                        match self.mode {
1824                            Mode::Normal => match self.tool {
1825                                Tool::Brush(ref mut brush) => {
1826                                    let color = if brush.is_set(BrushMode::Erase) {
1827                                        Rgba8::TRANSPARENT
1828                                    } else {
1829                                        self.fg
1830                                    };
1831                                    brush.start_drawing(p.into(), color, extent);
1832                                }
1833                                Tool::Sampler => {
1834                                    self.sample_color();
1835                                }
1836                                Tool::Pan(_) => {}
1837                            },
1838                            Mode::Command => {
1839                                // TODO
1840                            }
1841                            Mode::Visual(VisualState::Selecting { ref mut dragging }) => {
1842                                let p = p.map(|n| n as i32);
1843                                let unit = Selection::new(p.x, p.y, p.x + 1, p.y + 1);
1844
1845                                if let Some(s) = &mut self.selection {
1846                                    if s.abs().bounds().contains(p) {
1847                                        *dragging = true;
1848                                    } else {
1849                                        self.selection = Some(unit);
1850                                    }
1851                                } else {
1852                                    self.selection = Some(unit);
1853                                }
1854                            }
1855                            Mode::Visual(VisualState::Pasting) => {
1856                                self.command(Command::SelectionPaste);
1857                            }
1858                            Mode::Present | Mode::Help => {}
1859                        }
1860                    } else {
1861                        self.activate(id);
1862                        self.center_selection(self.cursor);
1863                    }
1864                } else {
1865                    // Clicking outside a view...
1866                    match self.mode {
1867                        Mode::Visual(VisualState::Selecting { ref mut dragging }) => {
1868                            self.selection = None;
1869                            *dragging = false;
1870                        }
1871                        _ => {}
1872                    }
1873                }
1874            }
1875            InputState::Released => match self.mode {
1876                Mode::Visual(VisualState::Selecting { ref mut dragging }) => {
1877                    *dragging = false;
1878                }
1879                Mode::Normal => {
1880                    if let Tool::Brush(ref mut brush) = self.tool {
1881                        match brush.state {
1882                            BrushState::Drawing { .. } | BrushState::DrawStarted { .. } => {
1883                                brush.stop_drawing();
1884                                self.active_view_mut().touch();
1885                            }
1886                            _ => {}
1887                        }
1888                    }
1889                }
1890                _ => {}
1891            },
1892            InputState::Repeated => {}
1893        }
1894    }
1895
1896    fn handle_mouse_wheel(&mut self, delta: platform::LogicalDelta) {
1897        if delta.y > 0. {
1898            if let Some(v) = self.hover_view {
1899                self.activate(v);
1900            }
1901            self.zoom_in(self.cursor);
1902        } else if delta.y < 0. {
1903            self.zoom_out(self.cursor);
1904        }
1905    }
1906
1907    fn handle_cursor_moved(&mut self, cursor: SessionCoords) {
1908        if self.cursor == cursor {
1909            return;
1910        }
1911
1912        let p = self.active_view_coords(cursor);
1913        let prev_p = self.active_view_coords(self.cursor);
1914        let (vw, vh) = self.active_view().size();
1915
1916        match self.tool {
1917            Tool::Pan(PanState::Panning) => {
1918                self.pan(cursor.x - self.cursor.x, cursor.y - self.cursor.y);
1919            }
1920            Tool::Sampler if self.mouse_state == InputState::Pressed => {
1921                self.sample_color();
1922            }
1923            _ => {
1924                match self.mode {
1925                    Mode::Normal => match self.tool {
1926                        Tool::Brush(ref mut brush) if p != prev_p => match brush.state {
1927                            BrushState::DrawStarted { .. } | BrushState::Drawing { .. } => {
1928                                let mut p: ViewCoords<i32> = p.into();
1929                                if brush.is_set(BrushMode::Multi) {
1930                                    p.clamp(Rect::new(
1931                                        (brush.size / 2) as i32,
1932                                        (brush.size / 2) as i32,
1933                                        vw as i32 - (brush.size / 2) as i32 - 1,
1934                                        vh as i32 - (brush.size / 2) as i32 - 1,
1935                                    ));
1936                                    brush.draw(p);
1937                                } else {
1938                                    brush.draw(p);
1939                                }
1940                            }
1941                            _ => {}
1942                        },
1943                        _ => {}
1944                    },
1945                    Mode::Visual(VisualState::Selecting { dragging: false }) => {
1946                        if self.mouse_state == InputState::Pressed {
1947                            if let Some(ref mut s) = self.selection {
1948                                *s = Selection::new(s.x1, s.y1, p.x as i32 + 1, p.y as i32 + 1);
1949                            }
1950                        }
1951                    }
1952                    Mode::Visual(VisualState::Selecting { dragging: true }) => {
1953                        let view = self.active_view().bounds();
1954
1955                        if self.mouse_state == InputState::Pressed && p != prev_p {
1956                            if let Some(ref mut s) = self.selection {
1957                                // TODO: (rgx) Better API.
1958                                let delta = *p - Vector2::new(prev_p.x, prev_p.y);
1959                                let delta = Vector2::new(delta.x as i32, delta.y as i32);
1960                                let t = Selection::from(s.bounds() + delta);
1961
1962                                if view.intersects(t.abs().bounds()) {
1963                                    *s = t;
1964                                }
1965                            }
1966                        }
1967                    }
1968                    Mode::Visual(VisualState::Pasting) => {
1969                        self.center_selection(cursor);
1970                    }
1971                    _ => {}
1972                }
1973            }
1974        }
1975
1976        self.cursor = cursor;
1977        self.cursor_dirty();
1978    }
1979
1980    fn handle_received_character(&mut self, c: char) {
1981        if self.mode == Mode::Command {
1982            if c.is_control() {
1983                return;
1984            }
1985            if self.ignore_received_characters {
1986                self.ignore_received_characters = false;
1987                return;
1988            }
1989            self.cmdline_handle_input(c);
1990        }
1991    }
1992
1993    fn handle_keyboard_input(&mut self, input: platform::KeyboardInput, exec: &mut Execution) {
1994        let KeyboardInput {
1995            state,
1996            modifiers,
1997            key,
1998            ..
1999        } = input;
2000
2001        let mut repeat = state == InputState::Repeated;
2002        let state = if repeat { InputState::Pressed } else { state };
2003
2004        if let Some(key) = key {
2005            // While the mouse is down, don't accept keyboard input.
2006            if self.mouse_state == InputState::Pressed {
2007                return;
2008            }
2009
2010            if state == InputState::Pressed {
2011                repeat = repeat || !self.keys_pressed.insert(key);
2012            } else if state == InputState::Released {
2013                if !self.keys_pressed.remove(&key) {
2014                    return;
2015                }
2016            }
2017
2018            match self.mode {
2019                Mode::Visual(VisualState::Selecting { .. }) => {
2020                    if key == platform::Key::Escape && state == InputState::Pressed {
2021                        self.switch_mode(Mode::Normal);
2022                        return;
2023                    }
2024                }
2025                Mode::Visual(VisualState::Pasting) => {
2026                    if key == platform::Key::Escape && state == InputState::Pressed {
2027                        self.switch_mode(Mode::Visual(VisualState::default()));
2028                        return;
2029                    }
2030                }
2031                Mode::Command => {
2032                    if state == InputState::Pressed {
2033                        match key {
2034                            platform::Key::Backspace => {
2035                                self.cmdline_handle_backspace();
2036                            }
2037                            platform::Key::Return => {
2038                                self.cmdline_handle_enter();
2039                            }
2040                            platform::Key::Escape => {
2041                                self.cmdline_hide();
2042                            }
2043                            _ => {}
2044                        }
2045                    }
2046                    return;
2047                }
2048                Mode::Help => {
2049                    if state == InputState::Pressed && key == platform::Key::Escape {
2050                        self.switch_mode(Mode::Normal);
2051                        return;
2052                    }
2053                }
2054                _ => {}
2055            }
2056
2057            if let Some(kb) = self
2058                .key_bindings
2059                .find(Key::Virtual(key), modifiers, state, self.mode)
2060            {
2061                // For toggle-like key bindings, we don't want to run the command
2062                // on key repeats. For regular key bindings, we run the command
2063                // depending on if it's supposed to repeat.
2064                if (repeat && kb.command.repeats() && !kb.is_toggle) || !repeat {
2065                    self.command(kb.command);
2066                }
2067                return;
2068            }
2069
2070            if let Execution::Recording { events, .. } = exec {
2071                if key == platform::Key::End {
2072                    events.pop(); // Discard this key event.
2073                    self.message("Saving recording...", MessageType::Info);
2074                    self.queue.push(InternalCommand::StopRecording);
2075                }
2076            }
2077        }
2078    }
2079
2080    ///////////////////////////////////////////////////////////////////////////
2081    /// Sourcing
2082    ///////////////////////////////////////////////////////////////////////////
2083
2084    /// Source an rx script at the given path. Returns an error if the path
2085    /// does not exist or the script couldn't be sourced.
2086    fn source_path<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
2087        let path = path.as_ref();
2088        debug!("source: {}", path.display());
2089
2090        File::open(&path)
2091            .or_else(|_| File::open(self.base_dirs.config_dir().join(path)))
2092            .and_then(|f| self.source_reader(io::BufReader::new(f), path))
2093            .map_err(|e| {
2094                io::Error::new(
2095                    e.kind(),
2096                    format!("error sourcing {}: {}", path.display(), e),
2097                )
2098            })
2099    }
2100
2101    /// Source a directory which contains a `.rxrc` script. Returns an
2102    /// error if the script wasn't found or couldn't be sourced.
2103    fn source_dir<P: AsRef<Path>>(&mut self, dir: P) -> io::Result<()> {
2104        self.source_path(dir.as_ref().join(".rxrc"))
2105    }
2106
2107    /// Source a script from an [`io::BufRead`].
2108    fn source_reader<P: AsRef<Path>, R: io::BufRead>(&mut self, r: R, _path: P) -> io::Result<()> {
2109        for (i, line) in r.lines().enumerate() {
2110            let line = line?;
2111
2112            if line.starts_with(cmd::COMMENT) {
2113                continue;
2114            }
2115            match Command::from_str(&format!(":{}", line)) {
2116                Err(e) => {
2117                    return Err(io::Error::new(
2118                        io::ErrorKind::Other,
2119                        format!("{} on line {}", e, i + 1),
2120                    ))
2121                }
2122                Ok(cmd) => self.command(cmd),
2123            }
2124        }
2125        Ok(())
2126    }
2127
2128    ///////////////////////////////////////////////////////////////////////////
2129    /// Centering
2130    ///////////////////////////////////////////////////////////////////////////
2131
2132    /// Center the palette in the workspace.
2133    fn center_palette(&mut self) {
2134        let n = usize::min(self.palette.size(), 16) as f32;
2135        let p = &mut self.palette;
2136
2137        p.x = 0.;
2138        p.y = self.height / 2. - n * p.cellsize / 2.;
2139    }
2140
2141    /// Vertically the active view in the workspace.
2142    fn center_active_view_v(&mut self) {
2143        let v = self.active_view();
2144        self.offset.y = (self.height / 2. - v.height() as f32 / 2. * v.zoom - v.offset.y).floor();
2145        self.cursor_dirty();
2146    }
2147
2148    /// Horizontally center the active view in the workspace.
2149    fn center_active_view_h(&mut self) {
2150        let v = self.active_view();
2151        self.offset.x = (self.width / 2. - v.width() as f32 * v.zoom / 2. - v.offset.x).floor();
2152        self.cursor_dirty();
2153    }
2154
2155    /// Center the active view in the workspace.
2156    fn center_active_view(&mut self) {
2157        self.center_active_view_v();
2158        self.center_active_view_h();
2159    }
2160
2161    /// The session center.
2162    #[allow(dead_code)]
2163    fn center(&self) -> SessionCoords {
2164        SessionCoords::new(self.width / 2., self.height / 2.)
2165    }
2166
2167    /// Center the selection to the given session coordinates.
2168    fn center_selection(&mut self, p: SessionCoords) {
2169        let c = self.active_view_coords(p);
2170        if let Some(ref mut s) = self.selection {
2171            let r = s.abs().bounds();
2172            let (w, h) = (r.width(), r.height());
2173            let (x, y) = (c.x as i32 - w / 2, c.y as i32 - h / 2);
2174            *s = Selection::new(x, y, x + w, y + h);
2175        }
2176    }
2177
2178    ///////////////////////////////////////////////////////////////////////////
2179    /// Zoom functions
2180    ///////////////////////////////////////////////////////////////////////////
2181
2182    /// Zoom the active view in.
2183    fn zoom_in(&mut self, center: SessionCoords) {
2184        let view = self.active_view_mut();
2185        let lvls = Self::ZOOM_LEVELS;
2186
2187        for (i, zoom) in lvls.iter().enumerate() {
2188            if view.zoom <= *zoom {
2189                if let Some(z) = lvls.get(i + 1) {
2190                    self.zoom(*z, center);
2191                } else {
2192                    self.message("Maximum zoom level reached", MessageType::Hint);
2193                }
2194                return;
2195            }
2196        }
2197    }
2198
2199    /// Zoom the active view out.
2200    fn zoom_out(&mut self, center: SessionCoords) {
2201        let view = self.active_view_mut();
2202        let lvls = Self::ZOOM_LEVELS;
2203
2204        for (i, zoom) in lvls.iter().enumerate() {
2205            if view.zoom <= *zoom {
2206                if i == 0 {
2207                    self.message("Minimum zoom level reached", MessageType::Hint);
2208                } else if let Some(z) = lvls.get(i - 1) {
2209                    self.zoom(*z, center);
2210                } else {
2211                    unreachable!();
2212                }
2213                return;
2214            }
2215        }
2216    }
2217
2218    /// Set the active view zoom. Takes a center to zoom to.
2219    fn zoom(&mut self, z: f32, center: SessionCoords) {
2220        let px = center.x - self.offset.x;
2221        let py = center.y - self.offset.y;
2222
2223        let zprev = self.active_view().zoom;
2224        let zdiff = z / zprev;
2225
2226        let nx = (px * zdiff).floor();
2227        let ny = (py * zdiff).floor();
2228
2229        let mut offset = Vector2::new(center.x - nx, center.y - ny);
2230
2231        let v = self.active_view_mut();
2232
2233        let vx = v.offset.x;
2234        let vy = v.offset.y;
2235
2236        v.zoom = z;
2237
2238        let dx = v.offset.x - (vx * zdiff);
2239        let dy = v.offset.y - (vy * zdiff);
2240
2241        offset.x -= dx;
2242        offset.y -= dy;
2243
2244        self.offset = offset.map(f32::floor);
2245        self.organize_views();
2246    }
2247
2248    ///////////////////////////////////////////////////////////////////////////
2249    /// Commands
2250    ///////////////////////////////////////////////////////////////////////////
2251
2252    /// Process a command.
2253    fn command(&mut self, cmd: Command) {
2254        debug!("command: {:?}", cmd);
2255
2256        match cmd {
2257            Command::Mode(m) => {
2258                self.toggle_mode(m);
2259            }
2260            Command::Quit => {
2261                self.quit_view_safe(self.views.active_id);
2262            }
2263            Command::QuitAll => {
2264                // TODO (rust)
2265                let ids: Vec<ViewId> = self.views.keys().cloned().collect();
2266                for id in ids {
2267                    self.quit_view_safe(id);
2268                }
2269            }
2270            Command::SwapColors => {
2271                std::mem::swap(&mut self.fg, &mut self.bg);
2272            }
2273            Command::BrushSet(mode) => {
2274                if let Tool::Brush(ref mut b) = self.tool {
2275                    b.set(mode);
2276                }
2277            }
2278            Command::BrushUnset(mode) => {
2279                if let Tool::Brush(ref mut b) = self.tool {
2280                    b.unset(mode);
2281                }
2282            }
2283            Command::BrushToggle(mode) => {
2284                if let Tool::Brush(ref mut b) = self.tool {
2285                    b.toggle(mode);
2286                }
2287            }
2288            Command::Brush => {
2289                self.unimplemented();
2290            }
2291            Command::BrushSize(op) => {
2292                if let Tool::Brush(ref mut b) = self.tool {
2293                    match op {
2294                        Op::Incr => {
2295                            b.size += 1;
2296                            b.size += b.size % 2;
2297                        }
2298                        Op::Decr => {
2299                            b.size -= 1;
2300                            b.size -= b.size % 2;
2301                        }
2302                        Op::Set(s) => {
2303                            b.size = s as usize;
2304                        }
2305                    }
2306                    if b.size < Self::MIN_BRUSH_SIZE {
2307                        b.size = Self::MIN_BRUSH_SIZE;
2308                    }
2309                }
2310            }
2311            Command::ResizeFrame(fw, fh) => {
2312                if fw == 0 || fh == 0 {
2313                    self.message(
2314                        "Error: cannot set frame dimension to `0`",
2315                        MessageType::Error,
2316                    );
2317                    return;
2318                }
2319                if fw > Self::MAX_FRAME_SIZE || fh > Self::MAX_FRAME_SIZE {
2320                    self.message(
2321                        format!(
2322                            "Error: maximum frame size is {}x{}",
2323                            Self::MAX_FRAME_SIZE,
2324                            Self::MAX_FRAME_SIZE,
2325                        ),
2326                        MessageType::Error,
2327                    );
2328                    return;
2329                }
2330
2331                let v = self.active_view_mut();
2332                v.resize_frames(fw, fh);
2333                v.touch();
2334
2335                self.check_selection();
2336                self.organize_views();
2337            }
2338            Command::ForceQuit => self.quit_view(self.views.active_id),
2339            Command::ForceQuitAll => self.quit(ExitReason::Normal),
2340            Command::Echo(ref v) => {
2341                let result = match v {
2342                    Value::Str(s) => Ok(Value::Str(s.clone())),
2343                    Value::Ident(s) => match s.as_str() {
2344                        "config/dir" => Ok(Value::Str(format!(
2345                            "{}",
2346                            self.base_dirs.config_dir().display()
2347                        ))),
2348                        "s/hidpi" => Ok(Value::Str(format!("{:.1}", self.hidpi_factor))),
2349                        "s/offset" => Ok(Value::F32Tuple(self.offset.x, self.offset.y)),
2350                        "v/offset" => {
2351                            let v = self.active_view();
2352                            Ok(Value::F32Tuple(v.offset.x, v.offset.y))
2353                        }
2354                        "v/zoom" => Ok(Value::F32(self.active_view().zoom as f64)),
2355                        _ => match self.settings.get(s) {
2356                            None => Err(format!("Error: {} is undefined", s)),
2357                            Some(result) => Ok(Value::Str(format!("{} = {}", v.clone(), result))),
2358                        },
2359                    },
2360                    _ => Err(format!("Error: argument cannot be echoed")),
2361                };
2362                match result {
2363                    Ok(v) => self.message(v, MessageType::Echo),
2364                    Err(e) => self.message(e, MessageType::Error),
2365                }
2366            }
2367            Command::PaletteAdd(rgba) => {
2368                self.palette.add(rgba);
2369                self.center_palette();
2370            }
2371            Command::PaletteClear => {
2372                self.palette.clear();
2373            }
2374            Command::PaletteSample => {
2375                self.unimplemented();
2376            }
2377            Command::Zoom(op) => {
2378                let center = if let Some(s) = self.selection {
2379                    self.session_coords(
2380                        self.views.active_id,
2381                        s.bounds().center().map(|n| n as f32).into(),
2382                    )
2383                } else if self.hover_view.is_some() {
2384                    self.cursor
2385                } else {
2386                    self.session_coords(self.views.active_id, self.active_view().center())
2387                };
2388
2389                match op {
2390                    Op::Incr => {
2391                        self.zoom_in(center);
2392                    }
2393                    Op::Decr => {
2394                        self.zoom_out(center);
2395                    }
2396                    Op::Set(z) => {
2397                        if z < 1. || z > Self::MAX_ZOOM {
2398                            self.message("Error: invalid zoom level", MessageType::Error);
2399                        } else {
2400                            self.zoom(z, center);
2401                        }
2402                    }
2403                }
2404            }
2405            Command::Reset => {
2406                if let Err(e) = self.reset() {
2407                    self.message(format!("Error: {}", e), MessageType::Error);
2408                } else {
2409                    self.message("Settings reset to default values", MessageType::Okay);
2410                }
2411            }
2412            Command::Fill(color) => {
2413                self.active_view_mut().clear(color);
2414            }
2415            Command::Pan(x, y) => {
2416                self.pan(
2417                    -(x * Self::PAN_PIXELS) as f32,
2418                    -(y * Self::PAN_PIXELS) as f32,
2419                );
2420            }
2421            Command::ViewNext => {
2422                let id = self.views.active_id;
2423
2424                if let Some(id) = self
2425                    .views
2426                    .range(id..)
2427                    .nth(1)
2428                    .map(|(id, _)| *id)
2429                    .or_else(|| self.views.keys().next().cloned())
2430                {
2431                    self.activate(id);
2432                    self.center_active_view();
2433                }
2434            }
2435            Command::ViewPrev => {
2436                let id = self.views.active_id;
2437
2438                if let Some(id) = self
2439                    .views
2440                    .range(..id)
2441                    .next_back()
2442                    .map(|(id, _)| *id)
2443                    .or_else(|| self.views.keys().next_back().cloned())
2444                {
2445                    self.activate(id);
2446                    self.center_active_view();
2447                }
2448            }
2449            Command::ViewCenter => {
2450                self.center_active_view();
2451            }
2452            Command::AddFrame => {
2453                self.active_view_mut().extend();
2454            }
2455            Command::CloneFrame(n) => {
2456                let v = self.active_view_mut();
2457                let l = v.animation.len() as i32;
2458                if n >= -1 && n < l {
2459                    v.extend_clone(n);
2460                } else {
2461                    self.message(
2462                        format!("Error: clone index must be in the range {}..{}", 0, l - 1),
2463                        MessageType::Error,
2464                    );
2465                }
2466            }
2467            Command::RemoveFrame => {
2468                self.active_view_mut().shrink();
2469                self.check_selection();
2470            }
2471            Command::Slice(None) => {
2472                let v = self.active_view_mut();
2473                v.slice(1);
2474                // FIXME: This is very inefficient. Since the actual frame contents
2475                // haven't changed, we don't need to create a full snapshot. We just
2476                // have to record how many frames are in this snapshot.
2477                v.touch();
2478            }
2479            Command::Slice(Some(nframes)) => {
2480                let v = self.active_view_mut();
2481                if !v.slice(nframes) {
2482                    self.message(
2483                        format!("Error: slice: view width is not divisible by {}", nframes),
2484                        MessageType::Error,
2485                    );
2486                } else {
2487                    // FIXME: This is very inefficient. Since the actual frame contents
2488                    // haven't changed, we don't need to create a full snapshot. We just
2489                    // have to record how many frames are in this snapshot.
2490                    v.touch();
2491                }
2492            }
2493            Command::Set(ref k, ref v) => {
2494                if Settings::DEPRECATED.contains(&k.as_str()) {
2495                    self.message(
2496                        format!("Warning: the setting `{}` has been deprecated", k),
2497                        MessageType::Warning,
2498                    );
2499                    return;
2500                }
2501                match self.settings.set(k, v.clone()) {
2502                    Err(e) => {
2503                        self.message(format!("Error: {}", e), MessageType::Error);
2504                    }
2505                    Ok(ref old) => {
2506                        if old != v {
2507                            self.setting_changed(k, old, v);
2508                        }
2509                    }
2510                }
2511            }
2512            #[allow(mutable_borrow_reservation_conflict)]
2513            Command::Toggle(ref k) => match self.settings.get(k) {
2514                Some(Value::Bool(b)) => self.command(Command::Set(k.clone(), Value::Bool(!b))),
2515                Some(_) => {
2516                    self.message(format!("Error: can't toggle `{}`", k), MessageType::Error);
2517                }
2518                None => {
2519                    self.message(
2520                        format!("Error: no such setting `{}`", k),
2521                        MessageType::Error,
2522                    );
2523                }
2524            },
2525            Command::Noop => {
2526                // Nothing happening!
2527            }
2528            Command::Source(ref path) => {
2529                if let Err(ref e) = self.source_path(path) {
2530                    self.message(
2531                        format!("Error sourcing `{}`: {}", path, e),
2532                        MessageType::Error,
2533                    );
2534                }
2535            }
2536            Command::Edit(ref paths) => {
2537                if paths.is_empty() {
2538                    self.unimplemented();
2539                } else if let Err(e) = self.edit(paths) {
2540                    self.message(format!("Error loading path(s): {}", e), MessageType::Error);
2541                }
2542            }
2543            Command::Write(None) => {
2544                if let Err(e) = self.save_view(self.views.active_id) {
2545                    self.message(format!("Error: {}", e), MessageType::Error);
2546                }
2547            }
2548            Command::Write(Some(ref path)) => {
2549                if let Err(e) = self.save_view_as(self.views.active_id, path) {
2550                    self.message(format!("Error: {}", e), MessageType::Error);
2551                }
2552            }
2553            Command::WriteQuit => {
2554                if self.save_view(self.views.active_id).is_ok() {
2555                    self.quit_view(self.views.active_id);
2556                }
2557            }
2558            Command::Map(map) => {
2559                let KeyMapping {
2560                    key,
2561                    press,
2562                    release,
2563                    modes,
2564                } = *map;
2565
2566                self.key_bindings.add(KeyBinding {
2567                    key,
2568                    modes: modes.clone(),
2569                    command: press,
2570                    state: InputState::Pressed,
2571                    modifiers: platform::ModifiersState::default(),
2572                    is_toggle: release.is_some(),
2573                    display: Some(format!("{}", key)),
2574                });
2575                if let Some(cmd) = release {
2576                    self.key_bindings.add(KeyBinding {
2577                        key,
2578                        modes,
2579                        command: cmd,
2580                        state: InputState::Released,
2581                        modifiers: platform::ModifiersState::default(),
2582                        is_toggle: true,
2583                        display: None,
2584                    });
2585                }
2586            }
2587            Command::MapClear => {
2588                self.key_bindings = KeyBindings::default();
2589            }
2590            Command::Undo => {
2591                self.undo(self.views.active_id);
2592            }
2593            Command::Redo => {
2594                self.redo(self.views.active_id);
2595            }
2596            Command::Tool(t) => {
2597                self.tool(t);
2598            }
2599            Command::ToolPrev => {
2600                self.prev_tool();
2601            }
2602            Command::Crop(_) => {
2603                self.unimplemented();
2604            }
2605            Command::SelectionMove(x, y) => {
2606                if let Some(ref mut s) = self.selection {
2607                    s.translate(x, y);
2608                }
2609            }
2610            Command::SelectionResize(x, y) => {
2611                if let Some(ref mut s) = self.selection {
2612                    s.resize(x, y);
2613                }
2614            }
2615            Command::SelectionExpand => {
2616                let v = self.active_view();
2617                let (fw, fh) = (v.fw as i32, v.fh as i32);
2618                let (vw, vh) = (v.width() as i32, v.height() as i32);
2619
2620                if let Some(ref mut selection) = self.selection {
2621                    let r = Rect::origin(vw, vh);
2622                    let s = selection.bounds();
2623                    let min = s.min();
2624                    let max = s.max();
2625
2626                    // If the selection is within the view rectangle, expand it,
2627                    // otherwise do nothing.
2628                    if r.contains(min) && r.contains(max.map(|n| n - 1)) {
2629                        let x1 = if min.x % fw == 0 {
2630                            min.x - fw
2631                        } else {
2632                            min.x - min.x % fw
2633                        };
2634                        let x2 = max.x + (fw - max.x % fw);
2635                        let y2 = fh;
2636
2637                        *selection = Selection::from(Rect::new(x1, 0, x2, y2).intersection(r));
2638                    }
2639                } else {
2640                    self.selection = Some(Selection::new(0, 0, fw, fh));
2641                }
2642            }
2643            Command::SelectionOffset(mut x, mut y) => {
2644                if let Some(s) = &mut self.selection {
2645                    let r = s.abs().bounds();
2646                    if r.width() <= 2 && x < 0 {
2647                        x = 0;
2648                    }
2649                    if r.height() <= 2 && y < 0 {
2650                        y = 0;
2651                    }
2652                    *s = Selection::from(s.bounds().expand(x, y, x, y));
2653                } else if self.hover_view == Some(self.views.active_id) {
2654                    let p = self.active_view_coords(self.cursor).map(|n| n as i32);
2655                    self.selection = Some(Selection::new(p.x, p.y, p.x + 1, p.y + 1));
2656                }
2657            }
2658            Command::SelectionJump(dir) => {
2659                let v = self.active_view();
2660                let r = v.bounds();
2661                let fw = v.extent().fw as i32;
2662                if let Some(s) = &mut self.selection {
2663                    let mut t = *s;
2664                    t.translate(fw * i32::from(dir), 0);
2665
2666                    if r.intersects(t.abs().bounds()) {
2667                        *s = t;
2668                    }
2669                }
2670            }
2671            Command::SelectionPaste => {
2672                if let (Mode::Visual(VisualState::Pasting), Some(s)) = (self.mode, self.selection) {
2673                    self.active_view_mut().paste(s.abs().bounds());
2674                } else {
2675                    // TODO: Enter paste mode?
2676                }
2677            }
2678            Command::SelectionYank => {
2679                self.yank_selection();
2680            }
2681            Command::SelectionCut => {
2682                // To mimick the behavior of `vi`, we yank the selection
2683                // before deleting it.
2684                if self.yank_selection().is_some() {
2685                    self.command(Command::SelectionErase);
2686                }
2687            }
2688            Command::SelectionFill(color) => {
2689                if let Some(s) = self.selection {
2690                    self.effects
2691                        .push(Effect::ViewPaintFinal(vec![Shape::Rectangle(
2692                            s.abs().bounds().map(|n| n as f32),
2693                            ZDepth::default(),
2694                            Rotation::ZERO,
2695                            Stroke::NONE,
2696                            Fill::Solid(color.unwrap_or(self.fg).into()),
2697                        )]));
2698                    self.active_view_mut().touch();
2699                }
2700            }
2701            Command::SelectionErase => {
2702                if let Some(s) = self.selection {
2703                    self.effects.extend_from_slice(&[
2704                        Effect::ViewBlendingChanged(Blending::constant()),
2705                        Effect::ViewPaintFinal(vec![Shape::Rectangle(
2706                            s.abs().bounds().map(|n| n as f32),
2707                            ZDepth::default(),
2708                            Rotation::ZERO,
2709                            Stroke::NONE,
2710                            Fill::Solid(Rgba8::TRANSPARENT.into()),
2711                        )]),
2712                    ]);
2713                    self.active_view_mut().touch();
2714                }
2715            }
2716        };
2717    }
2718
2719    fn cmdline_hide(&mut self) {
2720        self.switch_mode(self.prev_mode.unwrap_or(Mode::Normal));
2721    }
2722
2723    fn cmdline_handle_backspace(&mut self) {
2724        self.cmdline.delc();
2725
2726        if self.cmdline.is_empty() {
2727            self.cmdline_hide();
2728        }
2729    }
2730
2731    fn cmdline_handle_enter(&mut self) {
2732        let input = self.cmdline.input();
2733        // Always hide the command line before executing the command,
2734        // because commands will often require being in a specific mode, eg.
2735        // visual mode for commands that run on selections.
2736        self.cmdline_hide();
2737
2738        if input.is_empty() {
2739            return;
2740        }
2741
2742        match Command::from_str(&input) {
2743            Err(e) => self.message(format!("Error: {}", e), MessageType::Error),
2744            Ok(cmd) => self.command(cmd),
2745        }
2746    }
2747
2748    fn cmdline_handle_input(&mut self, c: char) {
2749        self.cmdline.putc(c);
2750        self.message_clear();
2751    }
2752
2753    fn tool(&mut self, t: Tool) {
2754        if std::mem::discriminant(&t) != std::mem::discriminant(&self.tool) {
2755            self.prev_tool = Some(self.tool.clone());
2756        }
2757        self.tool = t;
2758    }
2759
2760    fn prev_tool(&mut self) {
2761        self.tool = self.prev_tool.clone().unwrap_or(Tool::default());
2762    }
2763
2764    ///////////////////////////////////////////////////////////////////////////
2765    /// Color functions
2766    ///////////////////////////////////////////////////////////////////////////
2767
2768    /// Pick the given color as foreground color.
2769    fn pick_color(&mut self, color: Rgba8) {
2770        if color.a == 0x0 {
2771            return;
2772        }
2773        if color != self.fg {
2774            self.bg = self.fg;
2775            self.fg = color;
2776        }
2777        // TODO: Switch to brush.
2778    }
2779
2780    /// Get the color at the given view coordinate.
2781    pub fn color_at(&self, v: ViewId, p: ViewCoords<u32>) -> Option<Rgba8> {
2782        let resources = self.resources.lock();
2783        let (snapshot, pixels) = resources.get_snapshot(v);
2784
2785        let y_offset = snapshot
2786            .height()
2787            .checked_sub(p.y)
2788            .and_then(|x| x.checked_sub(1));
2789        let index = y_offset.map(|y| (y * snapshot.width() + p.x) as usize);
2790
2791        if let Some(bgra) = index.and_then(|idx| pixels.get(idx)) {
2792            Some(Rgba8::new(bgra.r, bgra.g, bgra.b, bgra.a))
2793        } else {
2794            None
2795        }
2796    }
2797
2798    fn sample_color(&mut self) {
2799        if let Some(color) = self.hover_color {
2800            self.pick_color(color);
2801        }
2802    }
2803}