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
|
/* Panel.h
Copyright (c) 2014 by Michael Zahniser
Endless Sky is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later version.
Endless Sky is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "MouseButton.h"
#include "Rectangle.h"
#include <functional>
#include <list>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include <SDL2/SDL.h>
class Command;
class Point;
class Sprite;
class TestContext;
class UI;
// Class representing a UI window (full screen or pop-up) which responds to user
// input and can draw itself. Everything displayed in the game is drawn in a
// Panel, and panels can stack on top of each other like "real" UI windows. By
// default, a panel allows the panels under it to show through, but does not
// allow them to receive any events that it does not know how to handle.
class Panel {
public:
// Draw a sprite repeatedly to make a vertical edge.
static void DrawEdgeSprite(const Sprite *edgeSprite, int posX);
public:
// Make the destructor virtual just in case any derived class needs it.
virtual ~Panel() = default;
// Move the state of this panel forward one game step.
virtual void Step();
// Draw this panel.
virtual void Draw() = 0;
// Return true if this is a full-screen panel, so there is no point in
// drawing any of the panels under it.
bool IsFullScreen() const noexcept;
// Return true if, when this panel is on the stack, no events should be
// passed to any panel under it. By default, all panels do this.
bool TrapAllEvents() const noexcept;
// Check if this panel can be "interrupted" to return to the main menu.
bool IsInterruptible() const noexcept;
// Clear the list of clickable zones.
void ClearZones();
// Add a clickable zone to the panel.
void AddZone(const Rectangle &rect, const std::function<void()> &fun);
void AddZone(const Rectangle &rect, SDL_Keycode key);
// Check if a click at the given coordinates triggers a clickable zone. If
// so, apply that zone's action and return true.
bool ZoneClick(const Point &point);
// Is fast-forward allowed to be on when this panel is on top of the GUI stack?
virtual bool AllowsFastForward() const noexcept;
virtual void UpdateTooltipActivation();
protected:
// Only override the ones you need; the default action is to return false.
virtual bool KeyDown(SDL_Keycode key, Uint16 mod, const Command &command, bool isNewPress);
virtual bool Click(int x, int y, MouseButton button, int clicks);
virtual bool Hover(int x, int y);
virtual bool Drag(double dx, double dy);
virtual bool Release(int x, int y, MouseButton button);
virtual bool Scroll(double dx, double dy);
virtual void Resize();
// If a clickable zone is clicked while editing is happening, the panel may
// need to know to exit editing mode before handling the click.
virtual void EndEditing() {}
void SetIsFullScreen(bool set);
void SetTrapAllEvents(bool set);
void SetInterruptible(bool set);
// Dim the background of this panel.
void DrawBackdrop() const;
UI *GetUI() const noexcept;
void SetUI(UI *ui);
// This is not for overriding, but for calling KeyDown with only one or two
// arguments. In this form, the command is never set, so you can call this
// with a key representing a known keyboard shortcut without worrying that a
// user-defined command key will override it.
bool DoKey(SDL_Keycode key, Uint16 mod = 0);
// A lot of different UI elements allow a modifier to change the number of
// something you are buying, so the shared function is defined here:
static int Modifier();
// Display the given help message if it has not yet been shown
// (or if force is set to true). Return true if the message was displayed.
bool DoHelp(const std::string &name, bool force = false) const;
const std::vector<std::shared_ptr<Panel>> &GetChildren();
// Add a child. Deferred until next frame.
void AddChild(const std::shared_ptr<Panel> &panel);
// Remove a child. Deferred until next frame.
void RemoveChild(const Panel *panel);
// Handle deferred add/remove child operations.
void AddOrRemove();
private:
class Zone : public Rectangle {
public:
Zone(const Rectangle &rect, const std::function<void()> &fun) : Rectangle(rect), fun(fun) {}
void Click() const { fun(); }
private:
std::function<void()> fun;
};
// The UI class will not directly call the virtual methods, but will call
// these instead. These methods will recursively allow child panels to
// handle the event first, before calling the virtual method for the derived
// class to handle it.
bool DoKeyDown(SDL_Keycode key, Uint16 mod, const Command &command, bool isNewPress);
bool DoClick(int x, int y, MouseButton button, int clicks);
bool DoHover(int x, int y);
bool DoDrag(double dx, double dy);
bool DoRelease(int x, int y, MouseButton button);
bool DoScroll(double dx, double dy);
void DoDraw();
void DoResize();
// Call a method on all the children in reverse order, and then on this
// object. Recursion stops as soon as any child returns true.
template<typename...FARGS, typename...ARGS>
bool EventVisit(bool(Panel::*f)(FARGS ...args), ARGS ...args);
private:
UI *ui = nullptr;
bool isFullScreen = false;
bool trapAllEvents = true;
bool isInterruptible = true;
std::list<Zone> zones;
std::vector<std::shared_ptr<Panel>> children;
std::vector<std::shared_ptr<Panel>> childrenToAdd;
std::vector<const Panel *> childrenToRemove;
friend class UI;
};
template<typename ...FARGS, typename ...ARGS>
bool Panel::EventVisit(bool (Panel::*f)(FARGS ...), ARGS ...args)
{
// Check if a child panel will consume this event first.
for(auto it = children.rbegin(); it != children.rend(); ++it)
if((*it)->EventVisit(f, args...))
return true;
// If none of our children handled this event, then it could be for us.
return (this->*f)(args...);
}
|