File: ui_manager.h

package info (click to toggle)
cataclysm-dda 0.H-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 710,808 kB
  • sloc: cpp: 524,019; python: 11,580; sh: 1,228; makefile: 1,169; xml: 507; javascript: 150; sql: 56; exp: 41; perl: 37
file content (282 lines) | stat: -rw-r--r-- 11,506 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
#pragma once
#ifndef CATA_SRC_UI_MANAGER_H
#define CATA_SRC_UI_MANAGER_H

#include <functional>

#include "cuboid_rectangle.h"
#include "point.h"

namespace catacurses
{
class window;
} // namespace catacurses

/**
 * Adaptor between UI code and the UI management system. Using this class allows
 * UIs to be correctly resized and redrawn when the game window is resized or
 * when exiting from other UIs.
 *
 * Usage example:
 * ```
 *     // Effective in the local scope
 *     ui_adaptor ui;
 *     // Ncurses window for drawing
 *     catacurses::window win;
 *     // Things to do when the game window changes size
 *     ui.on_screen_resize( [&]( ui_adaptor & ui ) {
 *         // Create an ncurses window
 *         win = catacurses::newwin( TERMX / 2, TERMY / 2, point( TERMX / 4, TERMY / 4 ) );
 *         // The window passed to this call must contain all the space the redraw
 *         // callback draws to, to ensure proper refreshing when resizing or exiting
 *         // from other UIs.
 *         ui.position_from_window( win );
 *     } );
 *     // Mark the resize callback to be called on the first redraw
 *     ui.mark_resize();
 *     // Things to do when redrawing the UI
 *     ui.on_redraw( [&]( ui_adaptor & ui ) {
 *         // Clear UI area
 *         werase( win );
 *         // Print things
 *         mvwprintw( win, point_zero, "Hello World!" );
 *         // Record the cursor position for screen readers and IME preview to
 *         // correctly function on curses
 *         ui.record_cursor( win );
 *         // Write to frame buffer
 *         wnoutrefresh( win );
 *     } );
 *
 *     input_context ctxt( "<CATEGORY_NAME>" );
 *     ctxt.register_action( "QUIT" );
 *     while( true ) {
 *         // Invalidate the top UI (that is, this UI) and redraw all
 *         // invalidated UIs (including lower UIs that calls this UI).
 *         // May call the resize callbacks.
 *         ui_manager::redraw();
 *         // Get user input. Note that this may call the resize and redraw
 *         // callbacks multiple times due to screen resize, rendering target
 *         // reset, etc.
 *         const std::string action = ctxt.handle_input();
 *         if( action == "QUIT" ) {
 *             break;
 *         }
 *     }
 * ```
 **/
class ui_adaptor
{
    public:
        using redraw_callback_t = std::function<void( ui_adaptor & )>;
        using screen_resize_callback_t = std::function<void( ui_adaptor & )>;

        struct disable_uis_below {
        };

        struct debug_message_ui {
        };

        /**
         * Construct a `ui_adaptor` which is automatically added to the UI stack,
         * and removed from the stack when it is deconstructed. (When declared as
         * a local variable, it is removed from the stack when leaving the local scope.)
         **/
        ui_adaptor();
        /**
         * `ui_adaptor` constructed this way will block any UIs below from being
         * redrawn or resized until it is deconstructed.
         **/
        explicit ui_adaptor( disable_uis_below );
        /**
         * Special constructor used by debug.cpp when showing a debug message
         * popup. If the UI is constructed when a redraw call is in progress,
         * The redraw call will be restarted after this UI is deconstructed and
         * the in progress callback returns, in order to prevent incorrect
         * graphics caused by overwritten screen area and resizing.
         **/
        explicit ui_adaptor( debug_message_ui );
        ui_adaptor( const ui_adaptor &rhs ) = delete;
        ui_adaptor( ui_adaptor &&rhs ) = delete;
        ~ui_adaptor();

        ui_adaptor &operator=( const ui_adaptor &rhs ) = delete;
        ui_adaptor &operator=( ui_adaptor &&rhs ) = delete;

        /**
         * Set the position and size of the UI to that of `win`. This information
         * is used to calculate which UIs need redrawing during resizing and when
         * exiting from other UIs, so do call this function in the resizing
         * callback and ensure `win` contains all the space you will be drawing
         * to. Transparency is not supported. If `win` is null, the function has
         * the same effect as `position( point_zero, point_zero )`
         **/
        void position_from_window( const catacurses::window &win );
        /**
         * Set the position and size of the UI to that of an imaginary
         * `catacurses::window` in normal font, except that the size can be zero.
         * Note that `topleft` and `size` are in console cells on both tiles
         * and curses builds.
         **/
        void position( const point &topleft, const point &size );
        /**
         * Set redraw and resize callbacks. The resize callback should
         * call `position` or `position_from_window` to set the size of the UI,
         * and (re-)calculate any UI data that is related to the screen size,
         * including `catacurses::window` instances. In most cases, you should
         * also call `mark_resize` along with `on_screen_resize` so the UI is
         * initialized by the resizing callback when redrawn for the first time.
         *
         * The redraw callback should only redraw to the area specified by the
         * `position` or `position_from_window` call. Content drawn outside this
         * area may not render correctly when resizing or exiting from other UIs.
         * Transparency is not currently supported.
         *
         * These callbacks should NOT:
         * - Construct new `ui_adaptor` instances
         * - Deconstruct old `ui_adaptor` instances
         * - Call `redraw` or `screen_resized`
         * - (Redraw callback) call `position_from_window`, `position`, or
         *   `invalidate_ui`
         * - Call any function that does these things, except for `debugmsg`
         *
         * Otherwise, display glitches or even crashes might happen.
         **/
        void on_redraw( const redraw_callback_t &fun );
        /* See `on_redraw`. */
        void on_screen_resize( const screen_resize_callback_t &fun );

        /**
         * Automatically set the termianl cursor to a window position after
         * drawing is done. The cursor position of the last drawn UI takes
         * effect. This ensures the cursor is always set to the desired position
         * even if some subsequent drawing code moves the cursor, so screen
         * readers (and in the case of text input, the IME preview) can
         * correctly function. This is only supposed to be called from the
         * `on_redraw` callback on the `ui_adaptor` argument of the callback.
         * Currently only supports curses (on tiles, the windows may have
         * different cell sizes, so the current implementation of recording the
         * on-screen position does not work, and screen readers do not work with
         * the current tiles implementation anyway).
         */
        void set_cursor( const catacurses::window &win, const point &pos );
        /**
         * Record the current window cursor position and restore it when drawing
         * is done. The most recent cursor position is recorded regardless of
         * whether the terminal cursor is updated by refreshing the window. See
         * also `set_cursor`.
         */
        void record_cursor( const catacurses::window &win );
        /**
         * Record the current terminal cursor position (where the cursor was
         * placed when refreshing the last window) and restore it when drawing
         * is done. See also `set_cursor`.
         */
        void record_term_cursor();
        /**
         * Use the terminal cursor position when the redraw callback returns.
         * This is the default. See also `set_cursor`.
         */
        void default_cursor();
        /**
         * Do not set cursor for this `ui_adaptor`. If any higher or lower UI
         * sets the cursor, that cursor position is used instead. If no UI sets
         * the cursor, the final cursor position when drawing is done is used.
         * See also `set_cursor`.
         */
        void disable_cursor();

        /**
         * Mark this `ui_adaptor` for resizing the next time it is redrawn.
         * This is normally called alongside `on_screen_resize` to initialize
         * the UI on the first redraw. You should also use this function to
         * explicitly request a reinitialization if any value the screen resize
         * callback depends on (apart from the screen size) has changed.
         **/
        void mark_resize() const;

        /**
         * Invalidate this UI so it gets redrawn the next redraw unless an upper
         * UI completely occludes this UI. May also cause upper UIs to redraw.
         * Can be used to mark lower UIs for redrawing when their associated data
         * has changed.
         **/
        void invalidate_ui() const;

        /**
         * Reset all callbacks and dimensions. Will cause invalidation of the
         * previously specified screen area.
         **/
        void reset();

        /* See the `ui_manager` namespace */
        static void invalidate( const rectangle<point> &rect, bool reenable_uis_below );
        static void redraw();
        static void redraw_invalidated();
        static void screen_resized();
    private:
        static void invalidation_consistency_and_optimization();

        // pixel dimensions in tiles, console cell dimensions in curses
        rectangle<point> dimensions;
        redraw_callback_t redraw_cb;
        screen_resize_callback_t screen_resized_cb;

        // For optimization purposes, these value may or may not be reset or
        // modified before, during, or after drawing, so they do not necessarily
        // indicate what the current cursor position would be, and therefore
        // should not be made public or have a public getter.
        enum class cursor {
            last, custom, disabled
        };
        cursor cursor_type = cursor::last;
        point cursor_pos;

        bool disabling_uis_below;
        bool is_debug_message_ui;

        mutable bool invalidated;
        mutable bool deferred_resize;
};

/**
 * Helper class that fills the background and obscures all UIs below. It stays
 * on the UI stack until its lifetime ends.
 **/
class background_pane
{
    public:
        background_pane();
    private:
        ui_adaptor ui;
};

// export static funcs of ui_adaptor with a more coherent scope name
namespace ui_manager
{
/**
 * Invalidate a portion of the screen when a UI is resized, closed, etc.
 * Not supposed to be directly called by the user.
 * rect is the pixel dimensions in tiles or console cell dimensions in curses
 **/
void invalidate( const rectangle<point> &rect, bool reenable_uis_below );
/**
 * Invalidate the top window and redraw all invalidated windows.
 * Note that `ui_manager` may redraw multiple times when a `ui_adaptor` callback
 * calls `debugmsg`, the game window is resized, or the system requests a redraw,
 * so any data that may change after a resize or on each redraw should be
 * calculated within the respective callbacks.
 **/
void redraw();
/**
 * Redraw all invalidated windows without invalidating the top window.
 **/
void redraw_invalidated();
/**
 * Handle resize of the game window.
 * Not supposed to be directly called by the user.
 **/
void screen_resized();
void invalidate_all_ui_adaptors();
} // namespace ui_manager

#endif // CATA_SRC_UI_MANAGER_H