1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/*
 *   Copyright (c) 2022 R3BL LLC
 *   All rights reserved.
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

use std::fmt::Debug;

use r3bl_rs_utils_core::*;
use serde::*;

use crate::*;

/// Please do not construct this struct directly, and use [new](DialogEngine::new) instead.
///
/// Holds data related to rendering in between render calls. This is not stored in the
/// [DialogBuffer] struct, which lives in the [r3bl_redux::Store]. The store provides the underlying
/// document or buffer struct that holds the actual document.
///
/// In order to change the document, you can use the
/// [DialogEngineApi::apply_event](DialogEngineApi::apply_event) method which takes [InputEvent] and
/// tries to execute it against this buffer.
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct DialogEngine {
    pub dialog_options: DialogEngineConfigOptions,
    pub editor_engine: EditorEngine,
    /// This [ColorWheel] is used to render the dialog box. It is created when
    /// [new()](DialogEngine::new) is called.
    /// - The colors it cycles through are "stable" meaning that once constructed via the
    ///   [ColorWheel::new()](ColorWheel::new) (which sets the options that determine where the
    ///   color wheel starts when it is used). For eg, between repeated calls to
    ///   [DialogEngineApi::render_engine](DialogEngineApi::render_engine) which uses the same
    ///   [ColorWheel] instance, the generated colors will be the same.
    /// - If you want to change where the color wheel "begins", you have to change
    ///   [ColorWheelConfig] options used to create this instance.
    pub color_wheel: ColorWheel,
    /// This is evaluated and saved when
    /// [DialogEngineApi::render_engine](DialogEngineApi::render_engine) is called. The dialog box
    /// is rendered outside of any layout [FlexBox] or [Surface], so it just paints itself to the
    /// screen on top of everything else.
    pub maybe_flex_box: Option<(
        /* window size: */ Size,
        /* mode: */ DialogEngineMode,
        /* flex box calculated by render_engine(): */ PartialFlexBox,
    )>,
    pub maybe_surface_bounds: Option<SurfaceBounds>,
    pub selected_row_index: ChUnit,
    pub scroll_offset_row_index: ChUnit,
}

impl DialogEngine {
    pub fn new(
        dialog_options: DialogEngineConfigOptions,
        editor_options: EditorEngineConfig,
    ) -> Self {
        // The col_count has to be large enough to fit the terminal width so that the gradient
        // doesn't flicker. If for some reason the terminal width is not available, then we default
        // to 250.
        let Size {
            col_count,
            row_count: _,
        } = lookup_size().unwrap_or(size!(col_count: 200, row_count: 0));

        Self {
            dialog_options,
            editor_engine: EditorEngine::new(editor_options),
            color_wheel: ColorWheel::new(vec![
                // Truecolor gradient.
                ColorWheelConfig::Rgb(
                    vec![
                        "#00ffff".into(), /* cyan  */
                        "#ff00ff".into(), /* magenta */
                        "#0000ff".into(), /* blue */
                        "#00ff00".into(), /* green */
                        "#ffff00".into(), /* yellow */
                        "#ff0000".into(), /* red */
                    ],
                    ColorWheelSpeed::Fast,
                    ch!(@to_usize col_count + 50),
                ),
                // Ansi256 gradient.
                ColorWheelConfig::Ansi256(
                    Ansi256GradientIndex::LightGreenToLightBlue,
                    ColorWheelSpeed::Medium,
                ),
            ]),
            ..Default::default()
        }
    }

    /// Clean up any state in the engine, eg: selected_row_index or scroll_offset_row_index.
    pub fn reset(&mut self) {
        self.selected_row_index = ch!(0);
        self.scroll_offset_row_index = ch!(0);
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct DialogEngineConfigOptions {
    pub mode: DialogEngineMode,
    /// Max height of the results panel.
    pub result_panel_display_row_count: ChUnit,
    pub maybe_style_border: Option<Style>,
    pub maybe_style_title: Option<Style>,
    pub maybe_style_editor: Option<Style>,
    pub maybe_style_results_panel: Option<Style>,
}

mod dialog_engine_config_options_impl {
    use super::*;

    impl Default for DialogEngineConfigOptions {
        fn default() -> Self {
            Self {
                mode: DialogEngineMode::ModalSimple,
                result_panel_display_row_count: ch!(
                    DisplayConstants::DefaultResultsPanelRowCount as u16
                ),
                maybe_style_border: None,
                maybe_style_editor: None,
                maybe_style_title: None,
                maybe_style_results_panel: None,
            }
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum DialogEngineMode {
    ModalSimple,
    ModalAutocomplete,
}