Merge commit 'd9b2c00228' as 'libraries/ZWidget'

This commit is contained in:
Rachael Alexanderson 2025-07-30 00:09:28 -04:00
commit 404123a22e
118 changed files with 26617 additions and 0 deletions

View file

@ -0,0 +1,116 @@
#pragma once
#include <memory>
#include <string>
#include <unordered_map>
#include "image.h"
#include "rect.h"
#include <vector>
class Font;
class Point;
class Rect;
class Colorf;
class DisplayWindow;
class CanvasFontGroup;
class CanvasTexture
{
public:
virtual ~CanvasTexture() = default;
int Width = 0;
int Height = 0;
};
class FontMetrics
{
public:
double ascent = 0.0;
double descent = 0.0;
double external_leading = 0.0;
double height = 0.0;
};
struct VerticalTextPosition
{
double top = 0.0;
double baseline = 0.0;
double bottom = 0.0;
};
class Canvas
{
public:
static std::unique_ptr<Canvas> create();
Canvas();
virtual ~Canvas();
virtual void attach(DisplayWindow* window);
virtual void detach();
virtual void begin(const Colorf& color);
virtual void end() { }
virtual void begin3d() { }
virtual void end3d() { }
Point getOrigin();
void setOrigin(const Point& origin);
void pushClip(const Rect& box);
void popClip();
void fillRect(const Rect& box, const Colorf& color);
void line(const Point& p0, const Point& p1, const Colorf& color);
void drawText(const Point& pos, const Colorf& color, const std::string& text);
Rect measureText(const std::string& text);
VerticalTextPosition verticalTextAlign();
void drawText(const std::shared_ptr<Font>& font, const Point& pos, const std::string& text, const Colorf& color);
void drawTextEllipsis(const std::shared_ptr<Font>& font, const Point& pos, const Rect& clipBox, const std::string& text, const Colorf& color);
Rect measureText(const std::shared_ptr<Font>& font, const std::string& text);
FontMetrics getFontMetrics(const std::shared_ptr<Font>& font);
int getCharacterIndex(const std::shared_ptr<Font>& font, const std::string& text, const Point& hitPoint);
void drawImage(const std::shared_ptr<Image>& image, const Point& pos);
void drawImage(const std::shared_ptr<Image>& image, const Rect& box);
protected:
virtual std::unique_ptr<CanvasTexture> createTexture(int width, int height, const void* pixels, ImageFormat format = ImageFormat::B8G8R8A8) = 0;
virtual void drawLineAntialiased(float x0, float y0, float x1, float y1, Colorf color) = 0;
virtual void fillTile(float x, float y, float width, float height, Colorf color) = 0;
virtual void drawTile(CanvasTexture* texture, float x, float y, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color) = 0;
virtual void drawGlyph(CanvasTexture* texture, float x, float y, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color) = 0;
int getClipMinX() const;
int getClipMinY() const;
int getClipMaxX() const;
int getClipMaxY() const;
template<typename T>
static T clamp(T val, T minval, T maxval) { return std::max<T>(std::min<T>(val, maxval), minval); }
DisplayWindow* window = nullptr;
int width = 0;
int height = 0;
double uiscale = 1.0f;
std::unique_ptr<CanvasTexture> whiteTexture;
private:
void setLanguage(const char* lang) { language = lang; }
void drawLineUnclipped(const Point& p0, const Point& p1, const Colorf& color);
std::unique_ptr<CanvasFontGroup> font;
Point origin;
std::vector<Rect> clipStack;
std::unordered_map<std::shared_ptr<Image>, std::unique_ptr<CanvasTexture>> imageTextures;
std::string language;
friend class CanvasFont;
};

View file

@ -0,0 +1,36 @@
#pragma once
#include <cstdint>
#include <cmath>
class Colorf
{
public:
Colorf() = default;
Colorf(float r, float g, float b, float a = 1.0f) : r(r), g(g), b(b), a(a) { }
static Colorf transparent() { return { 0.0f, 0.0f, 0.0f, 0.0f }; }
static Colorf fromRgba8(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255)
{
float s = 1.0f / 255.0f;
return { r * s, g * s, b * s, a * s };
}
uint32_t toBgra8() const
{
uint32_t cr = (int)(std::max(std::min(r * 255.0f, 255.0f), 0.0f));
uint32_t cg = (int)(std::max(std::min(g * 255.0f, 255.0f), 0.0f));
uint32_t cb = (int)(std::max(std::min(b * 255.0f, 255.0f), 0.0f));
uint32_t ca = (int)(std::max(std::min(a * 255.0f, 255.0f), 0.0f));
return (ca << 24) | (cr << 16) | (cg << 8) | cb;
}
bool operator==(const Colorf& v) const { return r == v.r && g == v.g && b == v.b && a == v.a; }
bool operator!=(const Colorf& v) const { return r != v.r || g != v.g || b != v.b || a != v.a; }
float r = 0.0f;
float g = 0.0f;
float b = 0.0f;
float a = 1.0f;
};

View file

@ -0,0 +1,15 @@
#pragma once
#include <memory>
#include <string>
class Font
{
public:
virtual ~Font() = default;
virtual const std::string& GetName() const = 0;
virtual double GetHeight() const = 0;
static std::shared_ptr<Font> Create(const std::string& name, double height);
};

View file

@ -0,0 +1,24 @@
#pragma once
#include <memory>
#include <string>
enum class ImageFormat
{
R8G8B8A8,
B8G8R8A8
};
class Image
{
public:
virtual ~Image() = default;
virtual int GetWidth() const = 0;
virtual int GetHeight() const = 0;
virtual ImageFormat GetFormat() const = 0;
virtual void* GetData() const = 0;
static std::shared_ptr<Image> Create(int width, int height, ImageFormat format, const void* data);
static std::shared_ptr<Image> LoadResource(const std::string& resourcename, double dpiscale = 1.0);
};

View file

@ -0,0 +1,96 @@
#pragma once
#include <vector>
#include <stdint.h>
#include <limits.h>
#include <string.h>
#include "core/rect.h"
/*
// 3x3 transform matrix
class PathFillTransform
{
public:
PathFillTransform() { for (int i = 0; i < 9; i++) matrix[i] = 0.0f; matrix[0] = matrix[4] = matrix[8] = 1.0; }
PathFillTransform(const float* mat3x3) { for (int i = 0; i < 9; i++) matrix[i] = (double)mat3x3[i]; }
PathFillTransform(const double* mat3x3) { for (int i = 0; i < 9; i++) matrix[i] = mat3x3[i]; }
double matrix[9];
};
*/
enum class PathFillMode
{
alternate,
winding
};
enum class PathFillCommand
{
line,
quadradic,
cubic
};
class PathFillSubpath
{
public:
PathFillSubpath() : points(1) { }
std::vector<Point> points;
std::vector<PathFillCommand> commands;
bool closed = false;
};
class PathFillDesc
{
public:
PathFillDesc() : subpaths(1) { }
PathFillMode fill_mode = PathFillMode::alternate;
std::vector<PathFillSubpath> subpaths;
void MoveTo(const Point& point)
{
if (!subpaths.back().commands.empty())
{
subpaths.push_back(PathFillSubpath());
}
subpaths.back().points.front() = point;
}
void LineTo(const Point& point)
{
auto& subpath = subpaths.back();
subpath.points.push_back(point);
subpath.commands.push_back(PathFillCommand::line);
}
void BezierTo(const Point& control, const Point& point)
{
auto& subpath = subpaths.back();
subpath.points.push_back(control);
subpath.points.push_back(point);
subpath.commands.push_back(PathFillCommand::quadradic);
}
void BezierTo(const Point& control1, const Point& control2, const Point& point)
{
auto& subpath = subpaths.back();
subpath.points.push_back(control1);
subpath.points.push_back(control2);
subpath.points.push_back(point);
subpath.commands.push_back(PathFillCommand::cubic);
}
void Close()
{
if (!subpaths.back().commands.empty())
{
subpaths.back().closed = true;
subpaths.push_back(PathFillSubpath());
}
}
void Rasterize(uint8_t* dest, int width, int height, bool blend = false);
};

View file

@ -0,0 +1,85 @@
#pragma once
#include <algorithm>
class Point
{
public:
Point() = default;
Point(double x, double y) : x(x), y(y) { }
double x = 0;
double y = 0;
Point& operator+=(const Point& p) { x += p.x; y += p.y; return *this; }
Point& operator-=(const Point& p) { x -= p.x; y -= p.y; return *this; }
};
class Size
{
public:
Size() = default;
Size(double width, double height) : width(width), height(height) { }
double width = 0;
double height = 0;
};
class Rect
{
public:
Rect() = default;
Rect(const Point& p, const Size& s) : x(p.x), y(p.y), width(s.width), height(s.height) { }
Rect(double x, double y, double width, double height) : x(x), y(y), width(width), height(height) { }
Point pos() const { return { x, y }; }
Size size() const { return { width, height }; }
Point topLeft() const { return { x, y }; }
Point topRight() const { return { x + width, y }; }
Point bottomLeft() const { return { x, y + height }; }
Point bottomRight() const { return { x + width, y + height }; }
double left() const { return x; }
double top() const { return y; }
double right() const { return x + width; }
double bottom() const { return y + height; }
static Rect xywh(double x, double y, double width, double height) { return Rect(x, y, width, height); }
static Rect ltrb(double left, double top, double right, double bottom) { return Rect(left, top, right - left, bottom - top); }
static Rect shrink(Rect box, double left, double top, double right, double bottom)
{
box.x += left;
box.y += top;
box.width = std::max(box.width - left - right, 0.0);
box.height = std::max(box.height - bottom - top, 0.0);
return box;
}
bool contains(const Point& p) const { return (p.x >= x && p.x < x + width) && (p.y >= y && p.y < y + height); }
double x = 0;
double y = 0;
double width = 0;
double height = 0;
};
inline Point operator+(const Point& a, const Point& b) { return Point(a.x + b.x, a.y + b.y); }
inline Point operator-(const Point& a, const Point& b) { return Point(a.x - b.x, a.y - b.y); }
inline bool operator==(const Point& a, const Point& b) { return a.x == b.x && a.y == b.y; }
inline bool operator!=(const Point& a, const Point& b) { return a.x != b.x || a.y != b.y; }
inline bool operator==(const Size& a, const Size& b) { return a.width == b.width && a.height == b.height; }
inline bool operator!=(const Size& a, const Size& b) { return a.width != b.width || a.height != b.height; }
inline bool operator==(const Rect& a, const Rect& b) { return a.x == b.x && a.y == b.y && a.width == b.width && a.height == b.height; }
inline bool operator!=(const Rect& a, const Rect& b) { return a.x != b.x || a.y != b.y || a.width != b.width || a.height != b.height; }
inline Point operator+(const Point& a, double b) { return Point(a.x + b, a.y + b); }
inline Point operator-(const Point& a, double b) { return Point(a.x - b, a.y - b); }
inline Point operator*(const Point& a, double b) { return Point(a.x * b, a.y * b); }
inline Point operator/(const Point& a, double b) { return Point(a.x / b, a.y / b); }
inline Size operator+(const Size& a, double b) { return Size(a.width + b, a.height + b); }
inline Size operator-(const Size& a, double b) { return Size(a.width - b, a.height - b); }
inline Size operator*(const Size& a, double b) { return Size(a.width * b, a.height * b); }
inline Size operator/(const Size& a, double b) { return Size(a.width / b, a.height / b); }

View file

@ -0,0 +1,14 @@
#pragma once
#include <vector>
#include <cstdint>
#include <string>
struct SingleFontData
{
std::vector<uint8_t> fontdata;
std::string language;
};
std::vector<SingleFontData> LoadWidgetFontData(const std::string& name);
std::vector<uint8_t> LoadWidgetData(const std::string& name);

View file

@ -0,0 +1,238 @@
#pragma once
#include <vector>
#include <algorithm>
#include "colorf.h"
#include "rect.h"
#include "font.h"
#include "canvas.h"
class Widget;
class Image;
class Canvas;
enum SpanAlign
{
span_left,
span_right,
span_center,
span_justify
};
class SpanLayout
{
public:
SpanLayout();
~SpanLayout();
struct HitTestResult
{
enum Type
{
no_objects_available,
outside_top,
outside_left,
outside_right,
outside_bottom,
inside
};
Type type = {};
int object_id = -1;
size_t offset = 0;
};
void Clear();
void AddText(const std::string& text, std::shared_ptr<Font> font, const Colorf& color = Colorf(), int id = -1);
void AddImage(const std::shared_ptr<Image> image, double baseline_offset = 0, int id = -1);
void AddWidget(Widget* component, double baseline_offset = 0, int id = -1);
void Layout(Canvas* canvas, double max_width);
void SetPosition(const Point& pos);
Size GetSize() const;
Rect GetRect() const;
std::vector<Rect> GetRectById(int id) const;
HitTestResult HitTest(Canvas* canvas, const Point& pos);
void DrawLayout(Canvas* canvas);
/// Draw layout generating ellipsis for clipped text
void DrawLayoutEllipsis(Canvas* canvas, const Rect& content_rect);
void SetComponentGeometry();
Size FindPreferredSize(Canvas* canvas);
void SetSelectionRange(std::string::size_type start, std::string::size_type end);
void SetSelectionColors(const Colorf& foreground, const Colorf& background);
void ShowCursor();
void HideCursor();
void SetCursorPos(std::string::size_type pos);
void SetCursorOverwriteMode(bool enable);
void SetCursorColor(const Colorf& color);
std::string GetCombinedText() const;
void SetAlign(SpanAlign align);
double GetFirstBaselineOffset();
double GetLastBaselineOffset();
private:
struct TextBlock
{
size_t start = 0;
size_t end = 0;
};
enum ObjectType
{
object_text,
object_image,
object_component
};
enum FloatType
{
float_none,
float_left,
float_right
};
struct SpanObject
{
ObjectType type = object_text;
FloatType float_type = float_none;
std::shared_ptr<Font> font;
Colorf color;
size_t start = 0, end = 0;
std::shared_ptr<Image> image;
Widget* component = nullptr;
double baseline_offset = 0;
int id = -1;
};
struct LineSegment
{
ObjectType type = object_text;
std::shared_ptr<Font> font;
Colorf color;
size_t start = 0;
size_t end = 0;
double ascender = 0;
double descender = 0;
double x_position = 0;
double width = 0;
std::shared_ptr<Image> image;
Widget* component = nullptr;
double baseline_offset = 0;
int id = -1;
};
struct Line
{
double width = 0; // Width of the entire line (including spaces)
double height = 0;
double ascender = 0;
std::vector<LineSegment> segments;
};
struct TextSizeResult
{
size_t start = 0;
size_t end = 0;
double width = 0;
double height = 0;
double ascender = 0;
double descender = 0;
int objects_traversed = 0;
std::vector<LineSegment> segments;
};
struct CurrentLine
{
std::vector<SpanObject>::size_type object_index = 0;
Line cur_line;
double x_position = 0;
double y_position = 0;
};
struct FloatBox
{
Rect rect;
ObjectType type = object_image;
std::shared_ptr<Image> image;
Widget* component = nullptr;
int id = -1;
};
TextSizeResult FindTextSize(Canvas* canvas, const TextBlock& block, size_t object_index);
std::vector<TextBlock> FindTextBlocks();
void LayoutLines(Canvas* canvas, double max_width);
void LayoutText(Canvas* canvas, std::vector<TextBlock> blocks, std::vector<TextBlock>::size_type block_index, CurrentLine& current_line, double max_width);
void LayoutBlock(CurrentLine& current_line, double max_width, std::vector<TextBlock>& blocks, std::vector<TextBlock>::size_type block_index);
void LayoutFloatBlock(CurrentLine& current_line, double max_width);
void LayoutInlineBlock(CurrentLine& current_line, double max_width, std::vector<TextBlock>& blocks, std::vector<TextBlock>::size_type block_index);
void ReflowLine(CurrentLine& current_line, double max_width);
FloatBox FloatBoxLeft(FloatBox float_box, double max_width);
FloatBox FloatBoxRight(FloatBox float_box, double max_width);
FloatBox FloatBoxAny(FloatBox box, double max_width, const std::vector<FloatBox>& floats1);
bool BoxFitsOnLine(const FloatBox& box, double max_width);
void PlaceLineSegments(CurrentLine& current_line, TextSizeResult& text_size_result);
void ForcePlaceLineSegments(CurrentLine& current_line, TextSizeResult& text_size_result, double max_width);
void NextLine(CurrentLine& current_line);
bool IsNewline(const TextBlock& block);
bool IsWhitespace(const TextBlock& block);
bool FitsOnLine(double x_position, const TextSizeResult& text_size_result, double max_width);
bool LargerThanLine(const TextSizeResult& text_size_result, double max_width);
void AlignJustify(double max_width);
void AlignCenter(double max_width);
void AlignRight(double max_width);
void DrawLayoutImage(Canvas* canvas, Line& line, LineSegment& segment, double x, double y);
void DrawLayoutText(Canvas* canvas, Line& line, LineSegment& segment, double x, double y);
bool cursor_visible = false;
std::string::size_type cursor_pos = 0;
bool cursor_overwrite_mode = false;
Colorf cursor_color;
std::string::size_type sel_start = 0, sel_end = 0;
Colorf sel_foreground, sel_background = Colorf::fromRgba8(153, 201, 239);
std::string text;
std::vector<SpanObject> objects;
std::vector<Line> lines;
Point position;
std::vector<FloatBox> floats_left, floats_right;
SpanAlign alignment = span_left;
struct LayoutCache
{
int object_index = -1;
FontMetrics metrics;
};
LayoutCache layout_cache;
bool is_ellipsis_draw = false;
Rect ellipsis_content_rect;
template<typename T>
static T clamp(T val, T minval, T maxval) { return std::max<T>(std::min<T>(val, maxval), minval); }
};

View file

@ -0,0 +1,81 @@
#pragma once
#include <memory>
#include <string>
#include <variant>
#include <unordered_map>
#include "rect.h"
#include "colorf.h"
class Widget;
class Canvas;
class WidgetStyle
{
public:
WidgetStyle(WidgetStyle* parentStyle = nullptr) : ParentStyle(parentStyle) { }
virtual ~WidgetStyle() = default;
virtual void Paint(Widget* widget, Canvas* canvas, Size size) = 0;
void SetBool(const std::string& state, const std::string& propertyName, bool value);
void SetInt(const std::string& state, const std::string& propertyName, int value);
void SetDouble(const std::string& state, const std::string& propertyName, double value);
void SetString(const std::string& state, const std::string& propertyName, const std::string& value);
void SetColor(const std::string& state, const std::string& propertyName, const Colorf& value);
void SetBool(const std::string& propertyName, bool value) { SetBool(std::string(), propertyName, value); }
void SetInt(const std::string& propertyName, int value) { SetInt(std::string(), propertyName, value); }
void SetDouble(const std::string& propertyName, double value) { SetDouble(std::string(), propertyName, value); }
void SetString(const std::string& propertyName, const std::string& value) { SetString(std::string(), propertyName, value); }
void SetColor(const std::string& propertyName, const Colorf& value) { SetColor(std::string(), propertyName, value); }
private:
// Note: do not call these directly. Use widget->GetStyleXX instead since a widget may explicitly override a class style
bool GetBool(const std::string& state, const std::string& propertyName) const;
int GetInt(const std::string& state, const std::string& propertyName) const;
double GetDouble(const std::string& state, const std::string& propertyName) const;
std::string GetString(const std::string& state, const std::string& propertyName) const;
Colorf GetColor(const std::string& state, const std::string& propertyName) const;
WidgetStyle* ParentStyle = nullptr;
typedef std::variant<bool, int, double, std::string, Colorf> PropertyVariant;
std::unordered_map<std::string, std::unordered_map<std::string, PropertyVariant>> StyleProperties;
const PropertyVariant* FindProperty(const std::string& state, const std::string& propertyName) const;
friend class Widget;
};
class BasicWidgetStyle : public WidgetStyle
{
public:
using WidgetStyle::WidgetStyle;
void Paint(Widget* widget, Canvas* canvas, Size size) override;
};
class WidgetTheme
{
public:
virtual ~WidgetTheme() = default;
WidgetStyle* RegisterStyle(std::unique_ptr<WidgetStyle> widgetStyle, const std::string& widgetClass);
WidgetStyle* GetStyle(const std::string& widgetClass);
static void SetTheme(std::unique_ptr<WidgetTheme> theme);
static WidgetTheme* GetTheme();
private:
std::unordered_map<std::string, std::unique_ptr<WidgetStyle>> Styles;
};
class DarkWidgetTheme : public WidgetTheme
{
public:
DarkWidgetTheme();
};
class LightWidgetTheme : public WidgetTheme
{
public:
LightWidgetTheme();
};

View file

@ -0,0 +1,26 @@
#pragma once
#include <functional>
class Widget;
class Timer
{
public:
Timer(Widget* owner);
~Timer();
void Start(int timeoutMilliseconds, bool repeat = true);
void Stop();
std::function<void()> FuncExpired;
private:
Widget* OwnerObj = nullptr;
Timer* PrevTimerObj = nullptr;
Timer* NextTimerObj = nullptr;
void* TimerId = nullptr;
friend class Widget;
};

View file

@ -0,0 +1,78 @@
/*
** Copyright (c) 1997-2015 Mark Page
**
** This software is provided 'as-is', without any express or implied
** warranty. In no event will the authors be held liable for any damages
** arising from the use of this software.
**
** Permission is granted to anyone to use this software for any purpose,
** including commercial applications, and to alter it and redistribute it
** freely, subject to the following restrictions:
**
** 1. The origin of this software must not be misrepresented; you must not
** claim that you wrote the original software. If you use this software
** in a product, an acknowledgment in the product documentation would be
** appreciated but is not required.
** 2. Altered source versions must be plainly marked as such, and must not be
** misrepresented as being the original software.
** 3. This notice may not be removed or altered from any source distribution.
**
*/
#pragma once
#include <string>
/// \brief UTF8 reader helper functions.
class UTF8Reader
{
public:
/// Important: text is not copied by this class and must remain valid during its usage.
UTF8Reader(const std::string::value_type *text, std::string::size_type length);
/// \brief Returns true if the current position is at the end of the string
bool is_end();
/// \brief Get the character at the current position
unsigned int character();
/// \brief Returns the length of the current character
std::string::size_type char_length();
/// \brief Moves position to the previous character
void prev();
/// \brief Moves position to the next character
void next();
/// \brief Moves position to the lead byte of the character
void move_to_leadbyte();
/// \brief Get the current position of the reader
std::string::size_type position();
/// \brief Set the current position of the reader
void set_position(std::string::size_type position);
static size_t utf8_length(const std::string& text)
{
return utf8_length(text.data(), text.size());
}
static size_t utf8_length(const std::string::value_type* text, std::string::size_type length)
{
UTF8Reader reader(text, length);
size_t i = 0;
while (!reader.is_end())
{
reader.next();
i++;
}
return i;
}
private:
std::string::size_type current_position = 0;
std::string::size_type length = 0;
const unsigned char *data = nullptr;
};

View file

@ -0,0 +1,241 @@
#pragma once
#include <string>
#include <memory>
#include <variant>
#include <unordered_map>
#include "canvas.h"
#include "rect.h"
#include "colorf.h"
#include "../window/window.h"
class Canvas;
class Timer;
enum class WidgetType
{
Child,
Window,
Popup
};
class Widget : DisplayWindowHost
{
public:
Widget(Widget* parent = nullptr, WidgetType type = WidgetType::Child, RenderAPI api = RenderAPI::Unspecified);
virtual ~Widget();
void SetParent(Widget* parent);
void MoveBefore(Widget* sibling);
std::string GetWindowTitle() const;
void SetWindowTitle(const std::string& text);
// Icon GetWindowIcon() const;
// void SetWindowIcon(const Icon& icon);
// Widget content box
Size GetSize() const;
double GetWidth() const { return GetSize().width; }
double GetHeight() const { return GetSize().height; }
// Widget noncontent area
void SetNoncontentSizes(double left, double top, double right, double bottom);
double GetNoncontentLeft() const { return GridFitSize(GetStyleDouble("noncontent-left")); }
double GetNoncontentTop() const { return GridFitSize(GetStyleDouble("noncontent-top")); }
double GetNoncontentRight() const { return GridFitSize(GetStyleDouble("noncontent-right")); }
double GetNoncontentBottom() const { return GridFitSize(GetStyleDouble("noncontent-bottom")); }
// Get the DPI scale factor for the window the widget is located on
double GetDpiScale() const;
// Align point to the nearest physical screen pixel
double GridFitPoint(double p) const;
Point GridFitPoint(double x, double y) const { return GridFitPoint(Point(x, y)); }
Point GridFitPoint(Point p) const { return Point(GridFitPoint(p.x), GridFitPoint(p.y)); }
// Convert size to exactly covering physical screen pixels
double GridFitSize(double s) const;
Size GridFitSize(double w, double h) const { return GridFitSize(Size(w, h)); }
Size GridFitSize(Size s) const { return Size(GridFitSize(s.width), GridFitSize(s.height)); }
// Widget frame box
Rect GetFrameGeometry() const;
void SetFrameGeometry(const Rect& geometry);
void SetFrameGeometry(double x, double y, double width, double height) { SetFrameGeometry(Rect::xywh(x, y, width, height)); }
// Style properties
void SetStyleClass(const std::string& styleClass);
const std::string& GetStyleClass() const { return StyleClass; }
void SetStyleState(const std::string& state);
const std::string& GetStyleState() const { return StyleState; }
void SetStyleBool(const std::string& propertyName, bool value);
void SetStyleInt(const std::string& propertyName, int value);
void SetStyleDouble(const std::string& propertyName, double value);
void SetStyleString(const std::string& propertyName, const std::string& value);
void SetStyleColor(const std::string& propertyName, const Colorf& value);
bool GetStyleBool(const std::string& propertyName) const;
int GetStyleInt(const std::string& propertyName) const;
double GetStyleDouble(const std::string& propertyName) const;
std::string GetStyleString(const std::string& propertyName) const;
Colorf GetStyleColor(const std::string& propertyName) const;
void SetWindowBackground(const Colorf& color);
void SetWindowBorderColor(const Colorf& color);
void SetWindowCaptionColor(const Colorf& color);
void SetWindowCaptionTextColor(const Colorf& color);
void SetVisible(bool enable) { if (enable) Show(); else Hide(); }
void Show();
void ShowFullscreen();
void ShowMaximized();
void ShowMinimized();
void ShowNormal();
void Hide();
void ActivateWindow();
void Close();
void Update();
void Repaint();
bool HasFocus();
bool IsEnabled();
bool IsVisible();
bool IsHidden();
bool IsFullscreen();
void SetFocus();
void SetEnabled(bool value);
void SetDisabled(bool value) { SetEnabled(!value); }
void SetHidden(bool value) { if (value) Hide(); else Show(); }
void LockCursor();
void UnlockCursor();
void SetCursor(StandardCursor cursor);
void SetPointerCapture();
void ReleasePointerCapture();
void SetModalCapture();
void ReleaseModalCapture();
bool GetKeyState(InputKey key);
std::string GetClipboardText();
void SetClipboardText(const std::string& text);
Widget* Window() const;
Canvas* GetCanvas() const;
Widget* ChildAt(double x, double y) { return ChildAt(Point(x, y)); }
Widget* ChildAt(const Point& pos);
bool IsParent(const Widget* w) const;
bool IsChild(const Widget* w) const;
Widget* Parent() const { return ParentObj; }
Widget* PrevSibling() const { return PrevSiblingObj; }
Widget* NextSibling() const { return NextSiblingObj; }
Widget* FirstChild() const { return FirstChildObj; }
Widget* LastChild() const { return LastChildObj; }
Point MapFrom(const Widget* parent, const Point& pos) const;
Point MapFromGlobal(const Point& pos) const;
Point MapFromParent(const Point& pos) const { return MapFrom(Parent(), pos); }
Point MapTo(const Widget* parent, const Point& pos) const;
Point MapToGlobal(const Point& pos) const;
Point MapToParent(const Point& pos) const { return MapTo(Parent(), pos); }
static Size GetScreenSize();
void SetCanvas(std::unique_ptr<Canvas> canvas);
void* GetNativeHandle();
int GetNativePixelWidth();
int GetNativePixelHeight();
// Vulkan support:
std::vector<std::string> GetVulkanInstanceExtensions() { return Window()->DispWindow->GetVulkanInstanceExtensions(); }
VkSurfaceKHR CreateVulkanSurface(VkInstance instance) { return Window()->DispWindow->CreateVulkanSurface(instance); }
protected:
virtual void OnPaintFrame(Canvas* canvas);
virtual void OnPaint(Canvas* canvas) { }
virtual bool OnMouseDown(const Point& pos, InputKey key) { return false; }
virtual bool OnMouseDoubleclick(const Point& pos, InputKey key) { return false; }
virtual bool OnMouseUp(const Point& pos, InputKey key) { return false; }
virtual bool OnMouseWheel(const Point& pos, InputKey key) { return false; }
virtual void OnMouseMove(const Point& pos) { }
virtual void OnMouseLeave() { }
virtual void OnRawMouseMove(int dx, int dy) { }
virtual void OnKeyChar(std::string chars) { }
virtual void OnKeyDown(InputKey key) { }
virtual void OnKeyUp(InputKey key) { }
virtual void OnGeometryChanged() { }
virtual void OnClose() { delete this; }
virtual void OnSetFocus() { }
virtual void OnLostFocus() { }
virtual void OnEnableChanged() { }
private:
void DetachFromParent();
void Paint(Canvas* canvas);
// DisplayWindowHost
void OnWindowPaint() override;
void OnWindowMouseMove(const Point& pos) override;
void OnWindowMouseLeave() override;
void OnWindowMouseDown(const Point& pos, InputKey key) override;
void OnWindowMouseDoubleclick(const Point& pos, InputKey key) override;
void OnWindowMouseUp(const Point& pos, InputKey key) override;
void OnWindowMouseWheel(const Point& pos, InputKey key) override;
void OnWindowRawMouseMove(int dx, int dy) override;
void OnWindowKeyChar(std::string chars) override;
void OnWindowKeyDown(InputKey key) override;
void OnWindowKeyUp(InputKey key) override;
void OnWindowGeometryChanged() override;
void OnWindowClose() override;
void OnWindowActivated() override;
void OnWindowDeactivated() override;
void OnWindowDpiScaleChanged() override;
WidgetType Type = {};
Widget* ParentObj = nullptr;
Widget* PrevSiblingObj = nullptr;
Widget* NextSiblingObj = nullptr;
Widget* FirstChildObj = nullptr;
Widget* LastChildObj = nullptr;
Timer* FirstTimerObj = nullptr;
Rect FrameGeometry = Rect::xywh(0.0, 0.0, 0.0, 0.0);
Rect ContentGeometry = Rect::xywh(0.0, 0.0, 0.0, 0.0);
Colorf WindowBackground = Colorf::fromRgba8(240, 240, 240);
std::string WindowTitle;
std::unique_ptr<DisplayWindow> DispWindow;
std::unique_ptr<Canvas> DispCanvas;
Widget* FocusWidget = nullptr;
Widget* CaptureWidget = nullptr;
Widget* HoverWidget = nullptr;
bool HiddenFlag = false;
StandardCursor CurrentCursor = StandardCursor::arrow;
std::string StyleClass = "widget";
std::string StyleState;
typedef std::variant<bool, int, double, std::string, Colorf> PropertyVariant;
std::unordered_map<std::string, PropertyVariant> StyleProperties;
Widget(const Widget&) = delete;
Widget& operator=(const Widget&) = delete;
friend class Timer;
friend class OpenFileDialog;
friend class OpenFolderDialog;
friend class SaveFileDialog;
};

View file

@ -0,0 +1,55 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
class Widget;
/// \brief Displays the system open file dialog.
class OpenFileDialog
{
public:
/// \brief Constructs an open file dialog.
static std::unique_ptr<OpenFileDialog> Create(Widget* owner);
virtual ~OpenFileDialog() = default;
/// \brief Get the full path of the file selected.
///
/// If multiple files are selected, this returns the first file.
virtual std::string Filename() const = 0;
/// \brief Gets an array that contains one file name for each selected file.
virtual std::vector<std::string> Filenames() const = 0;
/// \brief Sets if multiple files can be selected or not.
/// \param multiselect = When true, multiple items can be selected.
virtual void SetMultiSelect(bool multiselect) = 0;
/// \brief Sets a string containing the full path of the file selected.
virtual void SetFilename(const std::string &filename) = 0;
/// \brief Sets the default extension to use.
virtual void SetDefaultExtension(const std::string& extension) = 0;
/// \brief Add a filter that determines what types of files are displayed.
virtual void AddFilter(const std::string &filter_description, const std::string &filter_extension) = 0;
/// \brief Clears all filters.
virtual void ClearFilters() = 0;
/// \brief Sets a default filter, on a 0-based index.
virtual void SetFilterIndex(int filter_index) = 0;
/// \brief Sets the initial directory that is displayed.
virtual void SetInitialDirectory(const std::string &path) = 0;
/// \brief Sets the text that appears in the title bar.
virtual void SetTitle(const std::string &title) = 0;
/// \brief Shows the file dialog.
/// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise.
virtual bool Show() = 0;
};

View file

@ -0,0 +1,30 @@
#pragma once
#include <memory>
#include <string>
class Widget;
/// \brief Displays the system folder browsing dialog
class OpenFolderDialog
{
public:
/// \brief Constructs an browse folder dialog.
static std::unique_ptr<OpenFolderDialog> Create(Widget*owner);
virtual ~OpenFolderDialog() = default;
/// \brief Get the full path of the directory selected.
virtual std::string SelectedPath() const = 0;
/// \brief Sets the initial directory that is displayed.
virtual void SetInitialDirectory(const std::string& path) = 0;
/// \brief Sets the text that appears in the title bar.
virtual void SetTitle(const std::string& title) = 0;
/// \brief Shows the file dialog.
/// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise.
virtual bool Show() = 0;
};

View file

@ -0,0 +1,46 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
class Widget;
/// \brief Displays the system save file dialog.
class SaveFileDialog
{
public:
/// \brief Constructs a save file dialog.
static std::unique_ptr<SaveFileDialog> Create(Widget *owner);
virtual ~SaveFileDialog() = default;
/// \brief Get the full path of the file selected.
virtual std::string Filename() const = 0;
/// \brief Sets a string containing the full path of the file selected.
virtual void SetFilename(const std::string &filename) = 0;
/// \brief Sets the default extension to use.
virtual void SetDefaultExtension(const std::string& extension) = 0;
/// \brief Add a filter that determines what types of files are displayed.
virtual void AddFilter(const std::string &filter_description, const std::string &filter_extension) = 0;
/// \brief Clears all filters.
virtual void ClearFilters() = 0;
/// \brief Sets a default filter, on a 0-based index.
virtual void SetFilterIndex(int filter_index) = 0;
/// \brief Sets the initial directory that is displayed.
virtual void SetInitialDirectory(const std::string &path) = 0;
/// \brief Sets the text that appears in the title bar.
virtual void SetTitle(const std::string &title) = 0;
/// \brief Shows the file dialog.
/// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise.
virtual bool Show() = 0;
};

View file

@ -0,0 +1,34 @@
#pragma once
#include "../../core/widget.h"
class CheckboxLabel : public Widget
{
public:
CheckboxLabel(Widget* parent = nullptr);
void SetText(const std::string& value);
const std::string& GetText() const;
void SetChecked(bool value);
bool GetChecked() const;
void Toggle();
double GetPreferredHeight() const;
std::function<void(bool)> FuncChanged;
void SetRadioStyle(bool on) { radiostyle = on; }
protected:
void OnPaint(Canvas* canvas) override;
bool OnMouseDown(const Point& pos, InputKey key) override;
bool OnMouseUp(const Point& pos, InputKey key) override;
void OnMouseLeave() override;
void OnKeyUp(InputKey key) override;
private:
std::string text;
bool checked = false;
bool radiostyle = false;
bool mouseDownActive = false;
};

View file

@ -0,0 +1,31 @@
#pragma once
#include "../../core/widget.h"
#include "../../core/image.h"
enum ImageBoxMode
{
Center,
Contain,
Cover
};
class ImageBox : public Widget
{
public:
ImageBox(Widget* parent);
void SetImage(std::shared_ptr<Image> newImage);
void SetImageMode(ImageBoxMode mode);
double GetPreferredWidth() const;
double GetPreferredHeight() const;
protected:
void OnPaint(Canvas* canvas) override;
private:
std::shared_ptr<Image> image;
ImageBoxMode mode = ImageBoxMode::Center;
};

View file

@ -0,0 +1,158 @@
#pragma once
#include "../../core/widget.h"
#include "../../core/timer.h"
#include <functional>
class LineEdit : public Widget
{
public:
LineEdit(Widget* parent);
~LineEdit();
enum Alignment
{
align_left,
align_center,
align_right
};
Alignment GetAlignment() const;
bool IsReadOnly() const;
bool IsLowercase() const;
bool IsUppercase() const;
bool IsPasswordMode() const;
int GetMaxLength() const;
std::string GetText() const;
int GetTextInt() const;
float GetTextFloat() const;
std::string GetSelection() const;
int GetSelectionStart() const;
int GetSelectionLength() const;
int GetCursorPos() const;
Size GetTextSize();
Size GetTextSize(const std::string& str);
double GetPreferredContentWidth();
double GetPreferredContentHeight(double width);
void SetSelectAllOnFocusGain(bool enable);
void SelectAll();
void SetAlignment(Alignment alignment);
void SetReadOnly(bool enable = true);
void SetLowercase(bool enable = true);
void SetUppercase(bool enable = true);
void SetPasswordMode(bool enable = true);
void SetNumericMode(bool enable = true, bool decimals = false);
void SetMaxLength(int length);
void SetText(const std::string& text);
void SetTextInt(int number);
void SetTextFloat(float number, int num_decimal_places = 6);
void SetSelection(int pos, int length);
void ClearSelection();
void SetCursorPos(int pos);
void DeleteSelectedText();
void SetInputMask(const std::string& mask);
void SetDecimalCharacter(const std::string& decimal_char);
std::function<bool(InputKey key)> FuncIgnoreKeyDown;
std::function<std::string(std::string text)> FuncFilterKeyChar;
std::function<void()> FuncBeforeEditChanged;
std::function<void()> FuncAfterEditChanged;
std::function<void()> FuncSelectionChanged;
std::function<void()> FuncFocusGained;
std::function<void()> FuncFocusLost;
std::function<void()> FuncEnterPressed;
protected:
void OnPaint(Canvas* canvas) override;
void OnMouseMove(const Point& pos) override;
bool OnMouseDown(const Point& pos, InputKey key) override;
bool OnMouseDoubleclick(const Point& pos, InputKey key) override;
bool OnMouseUp(const Point& pos, InputKey key) override;
void OnKeyChar(std::string chars) override;
void OnKeyDown(InputKey key) override;
void OnKeyUp(InputKey key) override;
void OnGeometryChanged() override;
void OnEnableChanged() override;
void OnSetFocus() override;
void OnLostFocus() override;
private:
void OnTimerExpired();
void OnScrollTimerExpired();
void UpdateTextClipping();
void Move(int steps, bool ctrl, bool shift);
bool InsertText(int pos, const std::string& str);
void Backspace();
void Del();
int GetCharacterIndex(double x);
int FindNextBreakCharacter(int pos);
int FindPreviousBreakCharacter(int pos);
std::string GetVisibleTextBeforeSelection();
std::string GetVisibleTextAfterSelection();
std::string GetVisibleSelectedText();
std::string CreatePassword(std::string::size_type num_letters) const;
Size GetVisualTextSize(Canvas* canvas, int pos, int npos) const;
Size GetVisualTextSize(Canvas* canvas) const;
Rect GetCursorRect();
Rect GetSelectionRect();
bool InputMaskAcceptsInput(int cursor_pos, const std::string& str);
void SetSelectionStart(int start);
void SetSelectionLength(int length);
void SetTextSelection(int start, int length);
static std::string ToFixed(float number, int num_decimal_places);
static std::string ToLower(const std::string& text);
static std::string ToUpper(const std::string& text);
Timer* timer = nullptr;
std::string text;
Alignment alignment = align_left;
int cursor_pos = 0;
int max_length = -1;
bool mouse_selecting = false;
bool lowercase = false;
bool uppercase = false;
bool password_mode = false;
bool numeric_mode = false;
bool numeric_mode_decimals = false;
bool readonly = false;
int selection_start = -1;
int selection_length = 0;
std::string input_mask;
std::string decimal_char = ".";
VerticalTextPosition vertical_text_align;
Timer* scroll_timer = nullptr;
bool mouse_moves_left = false;
bool cursor_blink_visible = true;
unsigned int blink_timer = 0;
int clip_start_offset = 0;
int clip_end_offset = 0;
struct UndoInfo
{
/* set undo text when:
- added char after moving
- destructive block operation (del, cut etc)
- beginning erase
*/
std::string undo_text;
bool first_erase = false;
bool first_text_insert = false;
};
UndoInfo undo_info;
bool select_all_on_focus_gain = true;
static const std::string break_characters;
static const std::string numeric_mode_characters;
};

View file

@ -0,0 +1,46 @@
#pragma once
#include "../../core/widget.h"
#include <vector>
#include <functional>
class Scrollbar;
class ListView : public Widget
{
public:
ListView(Widget* parent = nullptr);
void SetColumnWidths(const std::vector<double>& widths);
void AddItem(const std::string& text, int index = -1, int column = 0);
void UpdateItem(const std::string& text, int index, int column = 0);
void RemoveItem(int index = -1);
size_t GetItemAmount() const { return items.size(); }
size_t GetColumnAmount() const { return columnwidths.size(); }
int GetSelectedItem() const { return selectedItem; }
void SetSelectedItem(int index);
void ScrollToItem(int index);
void Activate();
std::function<void(int)> OnChanged;
std::function<void()> OnActivated;
protected:
void OnPaint(Canvas* canvas) override;
bool OnMouseDown(const Point& pos, InputKey key) override;
bool OnMouseDoubleclick(const Point& pos, InputKey key) override;
bool OnMouseWheel(const Point& pos, InputKey key) override;
void OnKeyDown(InputKey key) override;
void OnGeometryChanged() override;
void OnScrollbarScroll();
static double getItemHeight();
Scrollbar* scrollbar = nullptr;
std::vector<std::vector<std::string>> items;
std::vector<double> columnwidths;
int selectedItem = 0;
};

View file

@ -0,0 +1,33 @@
#pragma once
#include "../../core/widget.h"
class Menubar;
class Toolbar;
class Statusbar;
class MainWindow : public Widget
{
public:
MainWindow(RenderAPI api = RenderAPI::Unspecified);
~MainWindow();
Menubar* GetMenubar() const { return MenubarWidget; }
Toolbar* GetTopToolbar() const { return TopToolbarWidget; }
Toolbar* GetLeftToolbar() const { return LeftToolbarWidget; }
Statusbar* GetStatusbar() const { return StatusbarWidget; }
Widget* GetCentralWidget() const { return CentralWidget; }
void SetCentralWidget(Widget* widget);
protected:
void OnGeometryChanged() override;
private:
Menubar* MenubarWidget = nullptr;
Toolbar* TopToolbarWidget = nullptr;
Toolbar* LeftToolbarWidget = nullptr;
Widget* CentralWidget = nullptr;
Statusbar* StatusbarWidget = nullptr;
};

View file

@ -0,0 +1,121 @@
#pragma once
#include "../../core/widget.h"
#include "../textlabel/textlabel.h"
#include "../imagebox/imagebox.h"
#include <vector>
class Menu;
class MenubarItem;
class MenuItem;
class MenuItemSeparator;
class Menubar : public Widget
{
public:
Menubar(Widget* parent);
~Menubar();
MenubarItem* AddItem(std::string text, std::function<void(Menu* menu)> onOpen, bool alignRight = false);
protected:
void OnGeometryChanged() override;
bool OnMouseDown(const Point& pos, InputKey key) override;
bool OnMouseUp(const Point& pos, InputKey key) override;
void OnMouseMove(const Point& pos) override;
void OnKeyDown(InputKey key) override;
private:
void ShowMenu(MenubarItem* item);
void CloseMenu();
MenubarItem* GetMenubarItemAt(const Point& pos);
int GetItemIndex(MenubarItem* item);
std::vector<MenubarItem*> menuItems;
int currentMenubarItem = -1;
Menu* openMenu = nullptr;
bool modalMode = false;
friend class MenubarItem;
};
class MenubarItem : public Widget
{
public:
MenubarItem(Menubar* menubar, std::string text, bool alignRight);
void SetOpenCallback(std::function<void(Menu* menu)> callback) { onOpen = std::move(callback); }
const std::function<void(Menu* menu)>& GetOpenCallback() const { return onOpen; }
double GetPreferredWidth() const;
bool AlignRight = false;
protected:
void OnPaint(Canvas* canvas) override;
bool OnMouseDown(const Point& pos, InputKey key) override;
bool OnMouseUp(const Point& pos, InputKey key) override;
void OnMouseMove(const Point& pos) override;
void OnMouseLeave() override;
private:
Menubar* menubar = nullptr;
std::function<void(Menu* menu)> onOpen;
std::string text;
};
class Menu : public Widget
{
public:
Menu(Widget* parent);
void SetLeftPosition(const Point& globalPos);
void SetRightPosition(const Point& globalPos);
MenuItem* AddItem(std::shared_ptr<Image> icon, std::string text, std::function<void()> onClick = {});
MenuItemSeparator* AddSeparator();
double GetPreferredWidth() const;
double GetPreferredHeight() const;
void SetSelected(MenuItem* item);
protected:
void OnGeometryChanged() override;
std::function<void()> onCloseMenu;
MenuItem* selectedItem = nullptr;
friend class Menubar;
};
class MenuItem : public Widget
{
public:
MenuItem(Menu* menu, std::function<void()> onClick);
void Click();
Menu* menu = nullptr;
ImageBox* icon = nullptr;
TextLabel* text = nullptr;
protected:
bool OnMouseUp(const Point& pos, InputKey key) override;
void OnMouseMove(const Point& pos) override;
void OnMouseLeave() override;
void OnGeometryChanged() override;
private:
std::function<void()> onClick;
};
class MenuItemSeparator : public Widget
{
public:
MenuItemSeparator(Widget* parent);
protected:
void OnPaint(Canvas* canvas) override;
};

View file

@ -0,0 +1,32 @@
#pragma once
#include "../../core/widget.h"
#include <functional>
class PushButton : public Widget
{
public:
PushButton(Widget* parent = nullptr);
void SetText(const std::string& value);
const std::string& GetText() const;
double GetPreferredHeight() const;
void Click();
std::function<void()> OnClick;
protected:
void OnPaint(Canvas* canvas) override;
bool OnMouseDown(const Point& pos, InputKey key) override;
bool OnMouseUp(const Point& pos, InputKey key) override;
void OnMouseMove(const Point& pos) override;
void OnMouseLeave() override;
void OnKeyDown(InputKey key) override;
void OnKeyUp(InputKey key) override;
private:
std::string text;
};

View file

@ -0,0 +1,99 @@
#pragma once
#include "../../core/widget.h"
#include "../../core/timer.h"
#include <functional>
class Scrollbar : public Widget
{
public:
Scrollbar(Widget* parent);
~Scrollbar();
bool IsVertical() const;
bool IsHorizontal() const;
double GetMin() const;
double GetMax() const;
double GetLineStep() const;
double GetPageStep() const;
double GetPosition() const;
void SetVertical();
void SetHorizontal();
void SetMin(double scroll_min);
void SetMax(double scroll_max);
void SetLineStep(double step);
void SetPageStep(double step);
void SetRanges(double scroll_min, double scroll_max, double line_step, double page_step);
void SetRanges(double view_size, double total_size);
void SetPosition(double pos);
double GetPreferredWidth() const { return 16.0; }
double GetPreferredHeight() const { return 16.0; }
std::function<void()> FuncScroll;
std::function<void()> FuncScrollMin;
std::function<void()> FuncScrollMax;
std::function<void()> FuncScrollLineDecrement;
std::function<void()> FuncScrollLineIncrement;
std::function<void()> FuncScrollPageDecrement;
std::function<void()> FuncScrollPageIncrement;
std::function<void()> FuncScrollThumbRelease;
std::function<void()> FuncScrollThumbTrack;
std::function<void()> FuncScrollEnd;
protected:
bool OnMouseDown(const Point& pos, InputKey key) override;
bool OnMouseUp(const Point& pos, InputKey key) override;
void OnMouseMove(const Point& pos) override;
void OnMouseLeave() override;
void OnPaint(Canvas* canvas) override;
void OnEnableChanged() override;
void OnGeometryChanged() override;
private:
bool UpdatePartPositions();
double CalculateThumbSize(double track_size);
double CalculateThumbPosition(double thumb_size, double track_size);
Rect CreateRect(double start, double end);
void InvokeScrollEvent(std::function<void()>* event_ptr);
void OnTimerExpired();
bool vertical = true;
double scroll_min = 0.0;
double scroll_max = 1.0;
double line_step = 1.0;
double page_step = 10.0;
double position = 0.0;
bool showbuttons = false;
enum MouseDownMode
{
mouse_down_none,
mouse_down_button_decr,
mouse_down_button_incr,
mouse_down_track_decr,
mouse_down_track_incr,
mouse_down_thumb_drag
} mouse_down_mode = mouse_down_none;
double thumb_start_position = 0.0;
Point mouse_drag_start_pos;
double thumb_start_pixel_position = 0.0;
Timer* mouse_down_timer = nullptr;
double last_step_size = 0.0;
Rect rect_button_decrement;
Rect rect_track_decrement;
Rect rect_thumb;
Rect rect_track_increment;
Rect rect_button_increment;
std::function<void()>* FuncScrollOnMouseDown = nullptr;
};

View file

@ -0,0 +1,19 @@
#pragma once
#include "../../core/widget.h"
class LineEdit;
class Statusbar : public Widget
{
public:
Statusbar(Widget* parent);
~Statusbar();
protected:
void OnPaint(Canvas* canvas) override;
private:
LineEdit* CommandEdit = nullptr;
};

View file

@ -0,0 +1,129 @@
#pragma once
#include "../../core/widget.h"
#include <functional>
#include <string>
#include <vector>
class TabBar;
class TabBarTab;
class TabBarSpacer;
class TabWidgetStack;
class TextLabel;
class ImageBox;
class Image;
class TabWidget : public Widget
{
public:
TabWidget(Widget* parent);
int AddTab(Widget* page, const std::string& label);
int AddTab(Widget* page, const std::shared_ptr<Image>& icon, const std::string& label);
void SetTabText(int index, const std::string& text);
void SetTabText(Widget* page, const std::string& text);
void SetTabIcon(int index, const std::shared_ptr<Image>& icon);
void SetTabIcon(Widget* page, const std::shared_ptr<Image>& icon);
int GetCurrentIndex() const;
Widget* GetCurrentWidget() const;
int GetPageIndex(Widget* pageWidget) const;
void SetCurrentIndex(int pageIndex);
void SetCurrentWidget(Widget* pageWidget);
std::function<void()> OnCurrentChanged;
protected:
void OnGeometryChanged() override;
private:
void OnBarCurrentChanged();
TabBar* Bar = nullptr;
TabWidgetStack* PageStack = nullptr;
std::vector<Widget*> Pages;
};
class TabBar : public Widget
{
public:
TabBar(Widget* parent);
int AddTab(const std::string& label);
int AddTab(const std::shared_ptr<Image>& icon, const std::string& label);
void SetTabText(int index, const std::string& text);
void SetTabIcon(int index, const std::shared_ptr<Image>& icon);
int GetCurrentIndex() const;
void SetCurrentIndex(int pageIndex);
double GetPreferredHeight() const { return 30.0; }
std::function<void()> OnCurrentChanged;
protected:
void OnGeometryChanged() override;
private:
void OnTabClicked(TabBarTab* tab);
int GetTabIndex(TabBarTab* tab);
int CurrentIndex = -1;
std::vector<TabBarTab*> Tabs;
TabBarSpacer* leftSpacer = nullptr;
TabBarSpacer* rightSpacer = nullptr;
};
class TabBarSpacer : public Widget
{
public:
TabBarSpacer(Widget* parent);
};
class TabBarTab : public Widget
{
public:
TabBarTab(Widget* parent);
void SetText(const std::string& text);
void SetIcon(const std::shared_ptr<Image>& icon);
void SetCurrent(bool value);
double GetPreferredWidth() const;
std::function<void()> OnClick;
protected:
void OnGeometryChanged() override;
bool OnMouseDown(const Point& pos, InputKey key) override;
bool OnMouseUp(const Point& pos, InputKey key) override;
void OnMouseMove(const Point& pos) override;
void OnMouseLeave() override;
private:
bool IsCurrent = false;
ImageBox* Icon = nullptr;
TextLabel* Label = nullptr;
bool hot = false;
};
class TabWidgetStack : public Widget
{
public:
TabWidgetStack(Widget* parent);
void SetCurrentWidget(Widget* widget);
Widget* GetCurrentWidget() const { return CurrentWidget; }
protected:
void OnGeometryChanged() override;
private:
Widget* CurrentWidget = nullptr;
};

View file

@ -0,0 +1,155 @@
#pragma once
#include "../../core/widget.h"
#include "../../core/timer.h"
#include "../../core/span_layout.h"
#include "../../core/font.h"
#include <functional>
class Scrollbar;
class TextEdit : public Widget
{
public:
TextEdit(Widget* parent);
~TextEdit();
bool IsReadOnly() const;
bool IsLowercase() const;
bool IsUppercase() const;
int GetMaxLength() const;
std::string GetText() const;
int GetLineCount() const;
std::string GetLineText(int line) const;
std::string GetSelection() const;
int GetSelectionStart() const;
int GetSelectionLength() const;
int GetCursorPos() const;
int GetCursorLineNumber() const;
double GetTotalHeight();
void SetSelectAllOnFocusGain(bool enable);
void SelectAll();
void SetReadOnly(bool enable = true);
void SetLowercase(bool enable = true);
void SetUppercase(bool enable = true);
void SetMaxLength(int length);
void SetText(const std::string& text);
void AddText(const std::string& text);
void SetSelection(int pos, int length);
void ClearSelection();
void SetCursorPos(int pos);
void DeleteSelectedText();
void SetInputMask(const std::string& mask);
void SetCursorDrawingEnabled(bool enable);
std::function<std::string(std::string text)> FuncFilterKeyChar;
std::function<void()> FuncBeforeEditChanged;
std::function<void()> FuncAfterEditChanged;
std::function<void()> FuncSelectionChanged;
std::function<void()> FuncFocusGained;
std::function<void()> FuncFocusLost;
std::function<void()> FuncEnterPressed;
protected:
void OnPaint(Canvas* canvas) override;
void OnMouseMove(const Point& pos) override;
bool OnMouseDown(const Point& pos, InputKey key) override;
bool OnMouseDoubleclick(const Point& pos, InputKey key) override;
bool OnMouseUp(const Point& pos, InputKey key) override;
void OnKeyChar(std::string chars) override;
void OnKeyDown(InputKey key) override;
void OnKeyUp(InputKey key) override;
void OnGeometryChanged() override;
void OnEnableChanged() override;
void OnSetFocus() override;
void OnLostFocus() override;
private:
void LayoutLines(Canvas* canvas);
void OnTimerExpired();
void OnScrollTimerExpired();
void CreateComponents();
void OnVerticalScroll();
void UpdateVerticalScroll();
void MoveVerticalScroll();
double GetTotalLineHeight();
struct Line
{
std::string text;
SpanLayout layout;
Rect box;
bool invalidated = true;
};
struct ivec2
{
ivec2() = default;
ivec2(int x, int y) : x(x), y(y) { }
int x = 0;
int y = 0;
bool operator==(const ivec2& b) const { return x == b.x && y == b.y; }
bool operator!=(const ivec2& b) const { return x != b.x || y != b.y; }
};
Scrollbar* vert_scrollbar;
Timer* timer = nullptr;
std::vector<Line> lines = { Line() };
ivec2 cursor_pos = { 0, 0 };
int max_length = -1;
bool mouse_selecting = false;
bool lowercase = false;
bool uppercase = false;
bool readonly = false;
ivec2 selection_start = { -1, 0 };
int selection_length = 0;
std::string input_mask;
static std::string break_characters;
void Move(int steps, bool shift, bool ctrl);
void InsertText(ivec2 pos, const std::string& str);
void Backspace();
void Del();
ivec2 GetCharacterIndex(Point mouse_wincoords);
ivec2 FindNextBreakCharacter(ivec2 pos);
ivec2 FindPreviousBreakCharacter(ivec2 pos);
bool InputMaskAcceptsInput(ivec2 cursor_pos, const std::string& str);
std::string::size_type ToOffset(ivec2 pos) const;
ivec2 FromOffset(std::string::size_type offset) const;
VerticalTextPosition vertical_text_align;
Timer* scroll_timer = nullptr;
bool mouse_moves_left = false;
bool cursor_blink_visible = true;
unsigned int blink_timer = 0;
int clip_start_offset = 0;
int clip_end_offset = 0;
bool ignore_mouse_events = false;
struct UndoInfo
{
/* set undo text when:
- added char after moving
- destructive block operation (del, cut etc)
- beginning erase
*/
std::string undo_text;
bool first_erase = false;
bool first_text_insert = false;
} undo_info;
bool select_all_on_focus_gain = false;
std::shared_ptr<Font> font = Font::Create("NotoSans", 12.0);
template<typename T>
static T clamp(T val, T minval, T maxval) { return std::max<T>(std::min<T>(val, maxval), minval); }
};

View file

@ -0,0 +1,33 @@
#pragma once
#include "../../core/widget.h"
enum class TextLabelAlignment
{
Left,
Center,
Right
};
class TextLabel : public Widget
{
public:
TextLabel(Widget* parent = nullptr);
void SetText(const std::string& value);
const std::string& GetText() const;
void SetTextAlignment(TextLabelAlignment alignment);
TextLabelAlignment GetTextAlignment() const;
double GetPreferredWidth() const;
double GetPreferredHeight() const;
protected:
void OnPaint(Canvas* canvas) override;
private:
std::string text;
TextLabelAlignment textAlignment = TextLabelAlignment::Left;
};

View file

@ -0,0 +1,31 @@
#pragma once
#include "../../core/widget.h"
enum class ToolbarDirection
{
Horizontal,
Vertical
};
class ToolbarButton;
class Toolbar : public Widget
{
public:
Toolbar(Widget* parent);
~Toolbar();
void SetDirection(ToolbarDirection direction);
ToolbarDirection GetDirection(ToolbarDirection) const { return direction; }
ToolbarButton* AddButton(std::string icon, std::string text, std::function<void()> onClicked = {});
protected:
void OnGeometryChanged() override;
private:
ToolbarDirection direction = ToolbarDirection::Horizontal;
std::vector<ToolbarButton*> buttons;
};

View file

@ -0,0 +1,34 @@
#pragma once
#include "../../core/widget.h"
#include "../textlabel/textlabel.h"
#include "../imagebox/imagebox.h"
class ToolbarButton : public Widget
{
public:
ToolbarButton(Widget* parent);
~ToolbarButton();
void SetIcon(std::string icon);
void SetText(std::string text);
void Click();
double GetPreferredWidth();
double GetPreferredHeight();
std::function<void()> OnClick;
protected:
void OnMouseMove(const Point& pos) override;
void OnMouseLeave() override;
bool OnMouseDown(const Point& pos, InputKey key) override;
bool OnMouseUp(const Point& pos, InputKey key) override;
void OnGeometryChanged() override;
private:
ImageBox* image = nullptr;
TextLabel* label = nullptr;
};

View file

@ -0,0 +1,12 @@
#pragma once
#include <string>
#include <vector>
struct SDL_Window;
class SDL2NativeHandle
{
public:
SDL_Window* window = nullptr;
};

View file

@ -0,0 +1,10 @@
#pragma once
#include <wayland-client-protocol.h>
class WaylandNativeHandle
{
public:
wl_display* display = nullptr;
wl_surface* surface = nullptr;
};

View file

@ -0,0 +1,9 @@
#pragma once
typedef struct HWND__* HWND;
class Win32NativeHandle
{
public:
HWND hwnd = {};
};

View file

@ -0,0 +1,243 @@
#pragma once
#include <memory>
#include <string>
#include <functional>
#include <cstdint>
#include <cstdlib>
#include "../core/rect.h"
#ifndef VULKAN_H_
#define VK_DEFINE_HANDLE(object) typedef struct object##_T* object;
#if defined(__LP64__) || defined(_WIN64) || defined(__x86_64__) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__)
#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef struct object##_T *object;
#else
#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object;
#endif
VK_DEFINE_HANDLE(VkInstance)
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSurfaceKHR)
#endif
class Widget;
class OpenFileDialog;
class SaveFileDialog;
class OpenFolderDialog;
enum class StandardCursor
{
arrow,
appstarting,
cross,
hand,
ibeam,
no,
size_all,
size_nesw,
size_ns,
size_nwse,
size_we,
uparrow,
wait
};
enum class InputKey : uint32_t
{
None, LeftMouse, RightMouse, Cancel,
MiddleMouse, Unknown05, Unknown06, Unknown07,
Backspace, Tab, Unknown0A, Unknown0B,
Unknown0C, Enter, Unknown0E, Unknown0F,
Shift, Ctrl, Alt, Pause,
CapsLock, Unknown15, Unknown16, Unknown17,
Unknown18, Unknown19, Unknown1A, Escape,
Unknown1C, Unknown1D, Unknown1E, Unknown1F,
Space, PageUp, PageDown, End,
Home, Left, Up, Right,
Down, Select, Print, Execute,
PrintScrn, Insert, Delete, Help,
_0, _1, _2, _3,
_4, _5, _6, _7,
_8, _9, Unknown3A, Unknown3B,
Unknown3C, Unknown3D, Unknown3E, Unknown3F,
Unknown40, A, B, C,
D, E, F, G,
H, I, J, K,
L, M, N, O,
P, Q, R, S,
T, U, V, W,
X, Y, Z, Unknown5B,
Unknown5C, Unknown5D, Unknown5E, Unknown5F,
NumPad0, NumPad1, NumPad2, NumPad3,
NumPad4, NumPad5, NumPad6, NumPad7,
NumPad8, NumPad9, GreyStar, GreyPlus,
Separator, GreyMinus, NumPadPeriod, GreySlash,
F1, F2, F3, F4,
F5, F6, F7, F8,
F9, F10, F11, F12,
F13, F14, F15, F16,
F17, F18, F19, F20,
F21, F22, F23, F24,
Unknown88, Unknown89, Unknown8A, Unknown8B,
Unknown8C, Unknown8D, Unknown8E, Unknown8F,
NumLock, ScrollLock, Unknown92, Unknown93,
Unknown94, Unknown95, Unknown96, Unknown97,
Unknown98, Unknown99, Unknown9A, Unknown9B,
Unknown9C, Unknown9D, Unknown9E, Unknown9F,
LShift, RShift, LControl, RControl,
UnknownA4, UnknownA5, UnknownA6, UnknownA7,
UnknownA8, UnknownA9, UnknownAA, UnknownAB,
UnknownAC, UnknownAD, UnknownAE, UnknownAF,
UnknownB0, UnknownB1, UnknownB2, UnknownB3,
UnknownB4, UnknownB5, UnknownB6, UnknownB7,
UnknownB8, UnknownB9, Semicolon, Equals,
Comma, Minus, Period, Slash,
Tilde, UnknownC1, UnknownC2, UnknownC3,
UnknownC4, UnknownC5, UnknownC6, UnknownC7,
Joy1, Joy2, Joy3, Joy4,
Joy5, Joy6, Joy7, Joy8,
Joy9, Joy10, Joy11, Joy12,
Joy13, Joy14, Joy15, Joy16,
UnknownD8, UnknownD9, UnknownDA, LeftBracket,
Backslash, RightBracket, SingleQuote, UnknownDF,
JoyX, JoyY, JoyZ, JoyR,
MouseX, MouseY, MouseZ, MouseW,
JoyU, JoyV, UnknownEA, UnknownEB,
MouseWheelUp, MouseWheelDown, Unknown10E, Unknown10F,
JoyPovUp, JoyPovDown, JoyPovLeft, JoyPovRight,
UnknownF4, UnknownF5, Attn, CrSel,
ExSel, ErEof, Play, Zoom,
NoName, PA1, OEMClear
};
enum class RenderAPI
{
Unspecified,
Bitmap,
Vulkan,
OpenGL,
D3D11,
D3D12,
Metal
};
class DisplayWindow;
class DisplayWindowHost
{
public:
virtual void OnWindowPaint() = 0;
virtual void OnWindowMouseMove(const Point& pos) = 0;
virtual void OnWindowMouseLeave() = 0;
virtual void OnWindowMouseDown(const Point& pos, InputKey key) = 0;
virtual void OnWindowMouseDoubleclick(const Point& pos, InputKey key) = 0;
virtual void OnWindowMouseUp(const Point& pos, InputKey key) = 0;
virtual void OnWindowMouseWheel(const Point& pos, InputKey key) = 0;
virtual void OnWindowRawMouseMove(int dx, int dy) = 0;
virtual void OnWindowKeyChar(std::string chars) = 0;
virtual void OnWindowKeyDown(InputKey key) = 0;
virtual void OnWindowKeyUp(InputKey key) = 0;
virtual void OnWindowGeometryChanged() = 0;
virtual void OnWindowClose() = 0;
virtual void OnWindowActivated() = 0;
virtual void OnWindowDeactivated() = 0;
virtual void OnWindowDpiScaleChanged() = 0;
};
class DisplayWindow
{
public:
static std::unique_ptr<DisplayWindow> Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI);
static void ProcessEvents();
static void RunLoop();
static void ExitLoop();
static void* StartTimer(int timeoutMilliseconds, std::function<void()> onTimer);
static void StopTimer(void* timerID);
static Size GetScreenSize();
virtual ~DisplayWindow() = default;
virtual void SetWindowTitle(const std::string& text) = 0;
virtual void SetWindowFrame(const Rect& box) = 0;
virtual void SetClientFrame(const Rect& box) = 0;
virtual void Show() = 0;
virtual void ShowFullscreen() = 0;
virtual void ShowMaximized() = 0;
virtual void ShowMinimized() = 0;
virtual void ShowNormal() = 0;
virtual bool IsWindowFullscreen() = 0;
virtual void Hide() = 0;
virtual void Activate() = 0;
virtual void ShowCursor(bool enable) = 0;
virtual void LockCursor() = 0;
virtual void UnlockCursor() = 0;
virtual void CaptureMouse() = 0;
virtual void ReleaseMouseCapture() = 0;
virtual void Update() = 0;
virtual bool GetKeyState(InputKey key) = 0;
virtual void SetCursor(StandardCursor cursor) = 0;
virtual Rect GetWindowFrame() const = 0;
virtual Size GetClientSize() const = 0;
virtual int GetPixelWidth() const = 0;
virtual int GetPixelHeight() const = 0;
virtual double GetDpiScale() const = 0;
virtual Point MapFromGlobal(const Point& pos) const = 0;
virtual Point MapToGlobal(const Point& pos) const = 0;
virtual void SetBorderColor(uint32_t bgra8) = 0;
virtual void SetCaptionColor(uint32_t bgra8) = 0;
virtual void SetCaptionTextColor(uint32_t bgra8) = 0;
virtual void PresentBitmap(int width, int height, const uint32_t* pixels) = 0;
virtual std::string GetClipboardText() = 0;
virtual void SetClipboardText(const std::string& text) = 0;
virtual void* GetNativeHandle() = 0;
virtual std::vector<std::string> GetVulkanInstanceExtensions() = 0;
virtual VkSurfaceKHR CreateVulkanSurface(VkInstance instance) = 0;
};
class DisplayBackend
{
public:
static DisplayBackend* Get();
static void Set(std::unique_ptr<DisplayBackend> instance);
static std::unique_ptr<DisplayBackend> TryCreateWin32();
static std::unique_ptr<DisplayBackend> TryCreateSDL2();
static std::unique_ptr<DisplayBackend> TryCreateX11();
static std::unique_ptr<DisplayBackend> TryCreateWayland();
static std::unique_ptr<DisplayBackend> TryCreateBackend();
virtual ~DisplayBackend() = default;
virtual bool IsWin32() { return false; }
virtual bool IsSDL2() { return false; }
virtual bool IsX11() { return false; }
virtual bool IsWayland() { return false; }
virtual std::unique_ptr<DisplayWindow> Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) = 0;
virtual void ProcessEvents() = 0;
virtual void RunLoop() = 0;
virtual void ExitLoop() = 0;
virtual void* StartTimer(int timeoutMilliseconds, std::function<void()> onTimer) = 0;
virtual void StopTimer(void* timerID) = 0;
virtual Size GetScreenSize() = 0;
virtual std::unique_ptr<OpenFileDialog> CreateOpenFileDialog(DisplayWindow* owner);
virtual std::unique_ptr<SaveFileDialog> CreateSaveFileDialog(DisplayWindow* owner);
virtual std::unique_ptr<OpenFolderDialog> CreateOpenFolderDialog(DisplayWindow* owner);
};

View file

@ -0,0 +1,13 @@
#pragma once
typedef struct _XDisplay Display;
typedef unsigned long XID;
typedef unsigned long VisualID;
typedef XID Window;
class X11NativeHandle
{
public:
Display* display = nullptr;
Window window = 0;
};