Add zwidget

This commit is contained in:
Magnus Norddahl 2023-12-27 00:44:40 +01:00 committed by Christoph Oelckers
parent e7285cd6d9
commit 113fdc5fcc
54 changed files with 9321 additions and 0 deletions

View file

@ -354,6 +354,8 @@ if (HAVE_VULKAN)
add_subdirectory( libraries/ZVulkan )
endif()
add_subdirectory( libraries/ZWidget )
add_subdirectory( libraries/discordrpc EXCLUDE_FROM_ALL )
set( DRPC_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries/discordrpc/include" )
set( DRPC_LIBRARIES discord-rpc )

View file

@ -0,0 +1,127 @@
cmake_minimum_required(VERSION 3.11)
project(zwidget)
set(ZWIDGET_SOURCES
src/core/canvas.cpp
src/core/font.cpp
src/core/image.cpp
src/core/span_layout.cpp
src/core/timer.cpp
src/core/widget.cpp
src/core/utf8reader.cpp
src/core/schrift/schrift.cpp
src/core/schrift/schrift.h
src/widgets/lineedit/lineedit.cpp
src/widgets/mainwindow/mainwindow.cpp
src/widgets/menubar/menubar.cpp
src/widgets/scrollbar/scrollbar.cpp
src/widgets/statusbar/statusbar.cpp
src/widgets/textedit/textedit.cpp
src/widgets/toolbar/toolbar.cpp
src/widgets/toolbar/toolbarbutton.cpp
src/widgets/imagebox/imagebox.cpp
src/widgets/textlabel/textlabel.cpp
src/widgets/pushbutton/pushbutton.cpp
src/widgets/checkboxlabel/checkboxlabel.cpp
src/widgets/listview/listview.cpp
src/window/window.cpp
)
set(ZWIDGET_INCLUDES
include/zwidget/core/canvas.h
include/zwidget/core/colorf.h
include/zwidget/core/font.h
include/zwidget/core/image.h
include/zwidget/core/rect.h
include/zwidget/core/span_layout.h
include/zwidget/core/timer.h
include/zwidget/core/widget.h
include/zwidget/core/utf8reader.h
include/zwidget/core/resourcedata.h
include/zwidget/widgets/lineedit/lineedit.h
include/zwidget/widgets/mainwindow/mainwindow.h
include/zwidget/widgets/menubar/menubar.h
include/zwidget/widgets/scrollbar/scrollbar.h
include/zwidget/widgets/statusbar/statusbar.h
include/zwidget/widgets/textedit/textedit.h
include/zwidget/widgets/toolbar/toolbar.h
include/zwidget/widgets/toolbar/toolbarbutton.h
include/zwidget/widgets/imagebox/imagebox.h
include/zwidget/widgets/textlabel/textlabel.h
include/zwidget/widgets/pushbutton/pushbutton.h
include/zwidget/widgets/checkboxlabel/checkboxlabel.h
include/zwidget/widgets/listview/listview.h
include/zwidget/window/window.h
)
set(ZWIDGET_WIN32_SOURCES
src/window/win32/win32window.cpp
src/window/win32/win32window.h
)
set(ZWIDGET_UNIX_SOURCES
)
source_group("src" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/.+")
source_group("src\\core" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/core/.+")
source_group("src\\core\\schrift" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/core/schrift/.+")
source_group("src\\widgets" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/.+")
source_group("src\\widgets\\lineedit" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/lineedit/.+")
source_group("src\\widgets\\mainwindow" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/mainwindow/.+")
source_group("src\\widgets\\menubar" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/menubar/.+")
source_group("src\\widgets\\scrollbar" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/scrollbar/.+")
source_group("src\\widgets\\statusbar" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/statusbar/.+")
source_group("src\\widgets\\textedit" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/textedit/.+")
source_group("src\\widgets\\toolbar" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/toolbar/.+")
source_group("src\\widgets\\imagebox" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/imagebox/.+")
source_group("src\\widgets\\textlabel" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/textlabel/.+")
source_group("src\\widgets\\pushbutton" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/pushbutton/.+")
source_group("src\\widgets\\checkboxlabel" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/checkboxlabel/.+")
source_group("src\\widgets\\listview" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/listview/.+")
source_group("src\\window" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/.+")
source_group("include" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/.+")
source_group("include\\core" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/core/.+")
source_group("include\\widgets" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/.+")
source_group("include\\widgets\\lineedit" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/lineedit/.+")
source_group("include\\widgets\\mainwindow" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/mainwindow/.+")
source_group("include\\widgets\\menubar" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/menubar/.+")
source_group("include\\widgets\\scrollbar" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/scrollbar/.+")
source_group("include\\widgets\\statusbar" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/statusbar/.+")
source_group("include\\widgets\\textedit" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/textedit/.+")
source_group("include\\widgets\\toolbar" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/toolbar/.+")
source_group("include\\widgets\\imagebox" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/imagebox/.+")
source_group("include\\widgets\\textlabel" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/textlabel/.+")
source_group("include\\widgets\\pushbutton" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/pushbutton/.+")
source_group("include\\widgets\\checkboxlabel" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/checkboxlabel/.+")
source_group("include\\widgets\\listview" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/listview/.+")
source_group("include\\window" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/.+")
source_group("include\\window\\win32" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/win32/.+")
source_group("include\\window\\unix" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/unix/.+")
include_directories(include include/zwidget src)
if(WIN32)
set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_WIN32_SOURCES})
add_definitions(-DUNICODE -D_UNICODE)
else()
set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_UNIX_SOURCES})
set(ZWIDGET_LIBS ${CMAKE_DL_LIBS} -ldl)
add_definitions(-DUNIX -D_UNIX)
add_link_options(-pthread)
endif()
if(MSVC)
# Use all cores for compilation
set(CMAKE_CXX_FLAGS "/MP ${CMAKE_CXX_FLAGS}")
# Ignore warnings in third party code
#set_source_files_properties(${ZWIDGET_SOURCES} PROPERTIES COMPILE_FLAGS "/wd4244 /wd4267 /wd4005 /wd4018 -D_CRT_SECURE_NO_WARNINGS")
endif()
add_library(zwidget STATIC ${ZWIDGET_SOURCES} ${ZWIDGET_INCLUDES})
target_link_libraries(zwidget ${ZWIDGET_LIBS})
set_target_properties(zwidget PROPERTIES CXX_STANDARD 17)
if(MSVC)
set_property(TARGET zwidget PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()

View file

@ -0,0 +1,21 @@
# License information
## License for ZWidget itself
// Copyright (c) 2023 Magnus Norddahl
//
// 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.

View file

@ -0,0 +1,2 @@
# ZWidget
A framework for building user interface applications

View file

@ -0,0 +1,63 @@
#pragma once
#include <memory>
#include <string>
class Font;
class Image;
class Point;
class Rect;
class Colorf;
class DisplayWindow;
struct VerticalTextPosition;
class FontMetrics
{
public:
double ascent = 0.0;
double descent = 0.0;
double external_leading = 0.0;
double height = 0.0;
};
class Canvas
{
public:
static std::unique_ptr<Canvas> create(DisplayWindow* window);
virtual ~Canvas() = default;
virtual void begin(const Colorf& color) = 0;
virtual void end() = 0;
virtual void begin3d() = 0;
virtual void end3d() = 0;
virtual Point getOrigin() = 0;
virtual void setOrigin(const Point& origin) = 0;
virtual void pushClip(const Rect& box) = 0;
virtual void popClip() = 0;
virtual void fillRect(const Rect& box, const Colorf& color) = 0;
virtual void line(const Point& p0, const Point& p1, const Colorf& color) = 0;
virtual void drawText(const Point& pos, const Colorf& color, const std::string& text) = 0;
virtual Rect measureText(const std::string& text) = 0;
virtual VerticalTextPosition verticalTextAlign() = 0;
virtual void drawText(const std::shared_ptr<Font>& font, const Point& pos, const std::string& text, const Colorf& color) = 0;
virtual void drawTextEllipsis(const std::shared_ptr<Font>& font, const Point& pos, const Rect& clipBox, const std::string& text, const Colorf& color) = 0;
virtual Rect measureText(const std::shared_ptr<Font>& font, const std::string& text) = 0;
virtual FontMetrics getFontMetrics(const std::shared_ptr<Font>& font) = 0;
virtual int getCharacterIndex(const std::shared_ptr<Font>& font, const std::string& text, const Point& hitPoint) = 0;
virtual void drawImage(const std::shared_ptr<Image>& image, const Point& pos) = 0;
};
struct VerticalTextPosition
{
double top = 0.0;
double baseline = 0.0;
double bottom = 0.0;
};

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,22 @@
#pragma once
#include <memory>
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);
};

View file

@ -0,0 +1,74 @@
#pragma once
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); }
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,7 @@
#pragma once
#include <vector>
#include <cstdint>
#include <string>
std::vector<uint8_t> LoadWidgetFontData(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,24 @@
#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;
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,181 @@
#pragma once
#include <string>
#include <memory>
#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);
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);
// 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)); }
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();
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 CaptureMouse();
void ReleaseMouseCapture();
bool GetKeyState(EInputKey key);
std::string GetClipboardText();
void SetClipboardText(const std::string& text);
Widget* Window();
Canvas* GetCanvas();
Widget* ChildAt(double x, double y) { return ChildAt(Point(x, y)); }
Widget* ChildAt(const Point& pos);
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); }
protected:
virtual void OnPaintFrame(Canvas* canvas) { }
virtual void OnPaint(Canvas* canvas) { }
virtual void OnMouseMove(const Point& pos) { }
virtual void OnMouseDown(const Point& pos, int key) { }
virtual void OnMouseDoubleclick(const Point& pos, int key) { }
virtual void OnMouseUp(const Point& pos, int key) { }
virtual void OnMouseWheel(const Point& pos, EInputKey key) { }
virtual void OnMouseLeave() { }
virtual void OnRawMouseMove(int dx, int dy) { }
virtual void OnKeyChar(std::string chars) { }
virtual void OnKeyDown(EInputKey key) { }
virtual void OnKeyUp(EInputKey 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 OnWindowMouseDown(const Point& pos, EInputKey key) override;
void OnWindowMouseDoubleclick(const Point& pos, EInputKey key) override;
void OnWindowMouseUp(const Point& pos, EInputKey key) override;
void OnWindowMouseWheel(const Point& pos, EInputKey key) override;
void OnWindowRawMouseMove(int dx, int dy) override;
void OnWindowKeyChar(std::string chars) override;
void OnWindowKeyDown(EInputKey key) override;
void OnWindowKeyUp(EInputKey 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);
struct
{
double Left = 0.0;
double Top = 0.0;
double Right = 0.0;
double Bottom = 0.0;
} Noncontent;
std::string WindowTitle;
std::unique_ptr<DisplayWindow> DispWindow;
std::unique_ptr<Canvas> DispCanvas;
Widget* FocusWidget = nullptr;
Widget* CaptureWidget = nullptr;
Widget(const Widget&) = delete;
Widget& operator=(const Widget&) = delete;
friend class Timer;
};

View file

@ -0,0 +1,25 @@
#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;
double GetPreferredHeight() const;
protected:
void OnPaint(Canvas* canvas) override;
private:
std::string text;
bool checked = false;
};

View file

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

View file

@ -0,0 +1,160 @@
#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(int 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 OnPaintFrame(Canvas* canvas) override;
void OnPaint(Canvas* canvas) override;
void OnMouseMove(const Point& pos) override;
void OnMouseDown(const Point& pos, int key) override;
void OnMouseDoubleclick(const Point& pos, int key) override;
void OnMouseUp(const Point& pos, int key) override;
void OnKeyChar(std::string chars) override;
void OnKeyDown(EInputKey key) override;
void OnKeyUp(EInputKey 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;
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;
};
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,19 @@
#pragma once
#include "../../core/widget.h"
#include <vector>
class ListView : public Widget
{
public:
ListView(Widget* parent = nullptr);
void AddItem(const std::string& text);
protected:
void OnPaint(Canvas* canvas) override;
void OnPaintFrame(Canvas* canvas) override;
std::vector<std::string> items;
};

View file

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

View file

@ -0,0 +1,14 @@
#pragma once
#include "../../core/widget.h"
class Menubar : public Widget
{
public:
Menubar(Widget* parent);
~Menubar();
protected:
void OnPaint(Canvas* canvas) override;
};

View file

@ -0,0 +1,22 @@
#pragma once
#include "../../core/widget.h"
class PushButton : public Widget
{
public:
PushButton(Widget* parent = nullptr);
void SetText(const std::string& value);
const std::string& GetText() const;
double GetPreferredHeight() const;
protected:
void OnPaintFrame(Canvas* canvas) override;
void OnPaint(Canvas* canvas) override;
private:
std::string text;
};

View file

@ -0,0 +1,97 @@
#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;
int GetMin() const;
int GetMax() const;
int GetLineStep() const;
int GetPageStep() const;
int GetPosition() const;
void SetVertical();
void SetHorizontal();
void SetMin(int scroll_min);
void SetMax(int scroll_max);
void SetLineStep(int step);
void SetPageStep(int step);
void SetRanges(int scroll_min, int scroll_max, int line_step, int page_step);
void SetRanges(int view_size, int total_size);
void SetPosition(int pos);
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:
void OnMouseMove(const Point& pos) override;
void OnMouseDown(const Point& pos, int key) override;
void OnMouseUp(const Point& pos, int key) override;
void OnMouseLeave() override;
void OnPaint(Canvas* canvas) override;
void OnEnableChanged() override;
void OnGeometryChanged() override;
private:
bool UpdatePartPositions();
int CalculateThumbSize(int track_size);
int CalculateThumbPosition(int thumb_size, int track_size);
Rect CreateRect(int start, int end);
void InvokeScrollEvent(std::function<void()>* event_ptr);
void OnTimerExpired();
bool vertical = false;
int scroll_min = 0;
int scroll_max = 1;
int line_step = 1;
int page_step = 10;
int position = 0;
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;
int thumb_start_position = 0;
Point mouse_drag_start_pos;
int thumb_start_pixel_position = 0;
Timer* mouse_down_timer = nullptr;
int last_step_size = 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;
static const int decr_height = 16;
static const int incr_height = 16;
};

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,156 @@
#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 OnPaintFrame(Canvas* canvas) override;
void OnPaint(Canvas* canvas) override;
void OnMouseMove(const Point& pos) override;
void OnMouseDown(const Point& pos, int key) override;
void OnMouseDoubleclick(const Point& pos, int key) override;
void OnMouseUp(const Point& pos, int key) override;
void OnKeyChar(std::string chars) override;
void OnKeyDown(EInputKey key) override;
void OnKeyUp(EInputKey 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("Segoe UI", 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,21 @@
#pragma once
#include "../../core/widget.h"
class TextLabel : public Widget
{
public:
TextLabel(Widget* parent = nullptr);
void SetText(const std::string& value);
const std::string& GetText() const;
double GetPreferredHeight() const;
protected:
void OnPaint(Canvas* canvas) override;
private:
std::string text;
};

View file

@ -0,0 +1,11 @@
#pragma once
#include "../../core/widget.h"
class Toolbar : public Widget
{
public:
Toolbar(Widget* parent);
~Toolbar();
};

View file

@ -0,0 +1,14 @@
#pragma once
#include "../../core/widget.h"
class ToolbarButton : public Widget
{
public:
ToolbarButton(Widget* parent);
~ToolbarButton();
protected:
void OnPaint(Canvas* canvas) override;
};

View file

@ -0,0 +1,173 @@
#pragma once
#include <memory>
#include <string>
#include "../core/rect.h"
class Engine;
enum class StandardCursor
{
arrow,
appstarting,
cross,
hand,
ibeam,
no,
size_all,
size_nesw,
size_ns,
size_nwse,
size_we,
uparrow,
wait
};
enum EInputKey
{
IK_None, IK_LeftMouse, IK_RightMouse, IK_Cancel,
IK_MiddleMouse, IK_Unknown05, IK_Unknown06, IK_Unknown07,
IK_Backspace, IK_Tab, IK_Unknown0A, IK_Unknown0B,
IK_Unknown0C, IK_Enter, IK_Unknown0E, IK_Unknown0F,
IK_Shift, IK_Ctrl, IK_Alt, IK_Pause,
IK_CapsLock, IK_Unknown15, IK_Unknown16, IK_Unknown17,
IK_Unknown18, IK_Unknown19, IK_Unknown1A, IK_Escape,
IK_Unknown1C, IK_Unknown1D, IK_Unknown1E, IK_Unknown1F,
IK_Space, IK_PageUp, IK_PageDown, IK_End,
IK_Home, IK_Left, IK_Up, IK_Right,
IK_Down, IK_Select, IK_Print, IK_Execute,
IK_PrintScrn, IK_Insert, IK_Delete, IK_Help,
IK_0, IK_1, IK_2, IK_3,
IK_4, IK_5, IK_6, IK_7,
IK_8, IK_9, IK_Unknown3A, IK_Unknown3B,
IK_Unknown3C, IK_Unknown3D, IK_Unknown3E, IK_Unknown3F,
IK_Unknown40, IK_A, IK_B, IK_C,
IK_D, IK_E, IK_F, IK_G,
IK_H, IK_I, IK_J, IK_K,
IK_L, IK_M, IK_N, IK_O,
IK_P, IK_Q, IK_R, IK_S,
IK_T, IK_U, IK_V, IK_W,
IK_X, IK_Y, IK_Z, IK_Unknown5B,
IK_Unknown5C, IK_Unknown5D, IK_Unknown5E, IK_Unknown5F,
IK_NumPad0, IK_NumPad1, IK_NumPad2, IK_NumPad3,
IK_NumPad4, IK_NumPad5, IK_NumPad6, IK_NumPad7,
IK_NumPad8, IK_NumPad9, IK_GreyStar, IK_GreyPlus,
IK_Separator, IK_GreyMinus, IK_NumPadPeriod, IK_GreySlash,
IK_F1, IK_F2, IK_F3, IK_F4,
IK_F5, IK_F6, IK_F7, IK_F8,
IK_F9, IK_F10, IK_F11, IK_F12,
IK_F13, IK_F14, IK_F15, IK_F16,
IK_F17, IK_F18, IK_F19, IK_F20,
IK_F21, IK_F22, IK_F23, IK_F24,
IK_Unknown88, IK_Unknown89, IK_Unknown8A, IK_Unknown8B,
IK_Unknown8C, IK_Unknown8D, IK_Unknown8E, IK_Unknown8F,
IK_NumLock, IK_ScrollLock, IK_Unknown92, IK_Unknown93,
IK_Unknown94, IK_Unknown95, IK_Unknown96, IK_Unknown97,
IK_Unknown98, IK_Unknown99, IK_Unknown9A, IK_Unknown9B,
IK_Unknown9C, IK_Unknown9D, IK_Unknown9E, IK_Unknown9F,
IK_LShift, IK_RShift, IK_LControl, IK_RControl,
IK_UnknownA4, IK_UnknownA5, IK_UnknownA6, IK_UnknownA7,
IK_UnknownA8, IK_UnknownA9, IK_UnknownAA, IK_UnknownAB,
IK_UnknownAC, IK_UnknownAD, IK_UnknownAE, IK_UnknownAF,
IK_UnknownB0, IK_UnknownB1, IK_UnknownB2, IK_UnknownB3,
IK_UnknownB4, IK_UnknownB5, IK_UnknownB6, IK_UnknownB7,
IK_UnknownB8, IK_UnknownB9, IK_Semicolon, IK_Equals,
IK_Comma, IK_Minus, IK_Period, IK_Slash,
IK_Tilde, IK_UnknownC1, IK_UnknownC2, IK_UnknownC3,
IK_UnknownC4, IK_UnknownC5, IK_UnknownC6, IK_UnknownC7,
IK_Joy1, IK_Joy2, IK_Joy3, IK_Joy4,
IK_Joy5, IK_Joy6, IK_Joy7, IK_Joy8,
IK_Joy9, IK_Joy10, IK_Joy11, IK_Joy12,
IK_Joy13, IK_Joy14, IK_Joy15, IK_Joy16,
IK_UnknownD8, IK_UnknownD9, IK_UnknownDA, IK_LeftBracket,
IK_Backslash, IK_RightBracket, IK_SingleQuote, IK_UnknownDF,
IK_JoyX, IK_JoyY, IK_JoyZ, IK_JoyR,
IK_MouseX, IK_MouseY, IK_MouseZ, IK_MouseW,
IK_JoyU, IK_JoyV, IK_UnknownEA, IK_UnknownEB,
IK_MouseWheelUp, IK_MouseWheelDown, IK_Unknown10E, IK_Unknown10F,
IK_JoyPovUp, IK_JoyPovDown, IK_JoyPovLeft, IK_JoyPovRight,
IK_UnknownF4, IK_UnknownF5, IK_Attn, IK_CrSel,
IK_ExSel, IK_ErEof, IK_Play, IK_Zoom,
IK_NoName, IK_PA1, IK_OEMClear
};
enum EInputType
{
IST_None,
IST_Press,
IST_Hold,
IST_Release,
IST_Axis
};
class KeyEvent
{
public:
EInputKey Key;
bool Ctrl = false;
bool Alt = false;
bool Shift = false;
Point MousePos = Point(0.0, 0.0);
};
class DisplayWindow;
class DisplayWindowHost
{
public:
virtual void OnWindowPaint() = 0;
virtual void OnWindowMouseMove(const Point& pos) = 0;
virtual void OnWindowMouseDown(const Point& pos, EInputKey key) = 0;
virtual void OnWindowMouseDoubleclick(const Point& pos, EInputKey key) = 0;
virtual void OnWindowMouseUp(const Point& pos, EInputKey key) = 0;
virtual void OnWindowMouseWheel(const Point& pos, EInputKey key) = 0;
virtual void OnWindowRawMouseMove(int dx, int dy) = 0;
virtual void OnWindowKeyChar(std::string chars) = 0;
virtual void OnWindowKeyDown(EInputKey key) = 0;
virtual void OnWindowKeyUp(EInputKey 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);
static void ProcessEvents();
static void RunLoop();
static void ExitLoop();
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 void Hide() = 0;
virtual void Activate() = 0;
virtual void ShowCursor(bool enable) = 0;
virtual void LockCursor() = 0;
virtual void UnlockCursor() = 0;
virtual void Update() = 0;
virtual bool GetKeyState(EInputKey key) = 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 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;
};

View file

@ -0,0 +1,696 @@
#include "core/canvas.h"
#include "core/rect.h"
#include "core/colorf.h"
#include "core/utf8reader.h"
#include "core/resourcedata.h"
#include "core/image.h"
#include "window/window.h"
#include "schrift/schrift.h"
#include <vector>
#include <unordered_map>
#include <stdexcept>
class CanvasTexture
{
public:
int Width = 0;
int Height = 0;
std::vector<uint32_t> Data;
};
class CanvasGlyph
{
public:
SFT_Glyph id;
SFT_GMetrics metrics;
double u = 0.0;
double v = 0.0;
double uvwidth = 0.0f;
double uvheight = 0.0f;
std::shared_ptr<CanvasTexture> texture;
};
class CanvasFont
{
public:
CanvasFont(const std::string& fontname, double height) : fontname(fontname), height(height)
{
data = LoadWidgetFontData(fontname);
loadFont(data.data(), data.size());
try
{
if (sft_lmetrics(&sft, &textmetrics) < 0)
throw std::runtime_error("Could not get truetype font metrics");
}
catch (...)
{
sft_freefont(sft.font);
throw;
}
}
~CanvasFont()
{
sft_freefont(sft.font);
sft.font = nullptr;
}
CanvasGlyph* getGlyph(uint32_t utfchar)
{
auto& glyph = glyphs[utfchar];
if (glyph)
return glyph.get();
glyph = std::make_unique<CanvasGlyph>();
if (sft_lookup(&sft, utfchar, &glyph->id) < 0)
return glyph.get();
if (sft_gmetrics(&sft, glyph->id, &glyph->metrics) < 0)
return glyph.get();
glyph->metrics.advanceWidth /= 3.0;
glyph->metrics.leftSideBearing /= 3.0;
if (glyph->metrics.minWidth <= 0 || glyph->metrics.minHeight <= 0)
return glyph.get();
int w = (glyph->metrics.minWidth + 3) & ~3;
int h = glyph->metrics.minHeight;
int destwidth = (w + 2) / 3;
auto texture = std::make_shared<CanvasTexture>();
texture->Width = destwidth;
texture->Height = h;
texture->Data.resize(destwidth * h);
uint32_t* dest = (uint32_t*)texture->Data.data();
std::unique_ptr<uint8_t[]> grayscalebuffer(new uint8_t[w * h]);
uint8_t* grayscale = grayscalebuffer.get();
SFT_Image img = {};
img.width = w;
img.height = h;
img.pixels = grayscale;
if (sft_render(&sft, glyph->id, img) < 0)
return glyph.get();
for (int y = 0; y < h; y++)
{
uint8_t* sline = grayscale + y * w;
uint32_t* dline = dest + y * destwidth;
for (int x = 2; x < w; x += 3)
{
uint32_t red = sline[x - 2];
uint32_t green = sline[x - 1];
uint32_t blue = sline[x];
uint32_t alpha = (red | green | blue) ? 255 : 0;
//red = (red + green) / 2;
//green = (red + green + blue) / 3;
//blue = (green + blue) / 2;
dline[x / 3] = (alpha << 24) | (red << 16) | (green << 8) | blue;
}
if (w % 3 == 1)
{
uint32_t red = sline[w - 1];
uint32_t green = 0;
uint32_t blue = 0;
uint32_t alpha = (red | green | blue) ? 255 : 0;
//red = (red + green) / 2;
//green = (red + green + blue) / 3;
//blue = (green + blue) / 2;
dline[(w - 1) / 3] = (alpha << 24) | (red << 16) | (green << 8) | blue;
}
else if (w % 3 == 2)
{
uint32_t red = sline[w - 2];
uint32_t green = sline[w - 1];
uint32_t blue = 0;
uint32_t alpha = (red | green | blue) ? 255 : 0;
//red = (red + green) / 2;
//green = (red + green + blue) / 3;
//blue = (green + blue) / 2;
dline[(w - 1) / 3] = (alpha << 24) | (red << 16) | (green << 8) | blue;
}
}
glyph->u = 0.0;
glyph->v = 0.0;
glyph->uvwidth = destwidth;
glyph->uvheight = h;
glyph->texture = std::move(texture);
return glyph.get();
}
std::string fontname;
double height = 0.0;
SFT_LMetrics textmetrics = {};
std::unordered_map<uint32_t, std::unique_ptr<CanvasGlyph>> glyphs;
private:
void loadFont(const void* data, size_t size)
{
sft.xScale = height * 3;
sft.yScale = height;
sft.flags = SFT_DOWNWARD_Y;
sft.font = sft_loadmem(data, size);
}
SFT sft = {};
std::vector<uint8_t> data;
};
class BitmapCanvas : public Canvas
{
public:
BitmapCanvas(DisplayWindow* window);
~BitmapCanvas();
void begin(const Colorf& color) override;
void end() override;
void begin3d() override;
void end3d() override;
Point getOrigin() override;
void setOrigin(const Point& origin) override;
void pushClip(const Rect& box) override;
void popClip() override;
void fillRect(const Rect& box, const Colorf& color) override;
void line(const Point& p0, const Point& p1, const Colorf& color) override;
void drawText(const Point& pos, const Colorf& color, const std::string& text) override;
Rect measureText(const std::string& text) override;
VerticalTextPosition verticalTextAlign() override;
void drawText(const std::shared_ptr<Font>& font, const Point& pos, const std::string& text, const Colorf& color) override { drawText(pos, color, text); }
void drawTextEllipsis(const std::shared_ptr<Font>& font, const Point& pos, const Rect& clipBox, const std::string& text, const Colorf& color) override { drawText(pos, color, text); }
Rect measureText(const std::shared_ptr<Font>& font, const std::string& text) override { return measureText(text); }
FontMetrics getFontMetrics(const std::shared_ptr<Font>& font) override
{
VerticalTextPosition vtp = verticalTextAlign();
FontMetrics metrics;
metrics.external_leading = vtp.top;
metrics.ascent = vtp.baseline - vtp.top;
metrics.descent = vtp.bottom - vtp.baseline;
metrics.height = metrics.ascent + metrics.descent;
return metrics;
}
int getCharacterIndex(const std::shared_ptr<Font>& font, const std::string& text, const Point& hitPoint) override { return 0; }
void drawImage(const std::shared_ptr<Image>& image, const Point& pos) override;
void drawLineUnclipped(const Point& p0, const Point& p1, const Colorf& color);
void drawTile(CanvasTexture* texture, float x, float y, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color);
void drawGlyph(CanvasTexture* texture, float x, float y, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color);
int getClipMinX() const;
int getClipMinY() const;
int getClipMaxX() const;
int getClipMaxY() const;
std::unique_ptr<CanvasTexture> createTexture(int width, int height, const void* pixels, ImageFormat format = ImageFormat::B8G8R8A8);
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;
std::unique_ptr<CanvasFont> font;
std::unique_ptr<CanvasTexture> whiteTexture;
Point origin;
double uiscale = 1.0f;
std::vector<Rect> clipStack;
int width = 0;
int height = 0;
std::vector<uint32_t> pixels;
std::unordered_map<std::shared_ptr<Image>, std::unique_ptr<CanvasTexture>> imageTextures;
};
BitmapCanvas::BitmapCanvas(DisplayWindow* window) : window(window)
{
uiscale = window->GetDpiScale();
uint32_t white = 0xffffffff;
whiteTexture = createTexture(1, 1, &white);
font = std::make_unique<CanvasFont>("Segoe UI", 13.0*uiscale);
}
BitmapCanvas::~BitmapCanvas()
{
}
Point BitmapCanvas::getOrigin()
{
return origin;
}
void BitmapCanvas::setOrigin(const Point& newOrigin)
{
origin = newOrigin;
}
void BitmapCanvas::pushClip(const Rect& box)
{
if (!clipStack.empty())
{
const Rect& clip = clipStack.back();
double x0 = box.x + origin.x;
double y0 = box.y + origin.y;
double x1 = x0 + box.width;
double y1 = y0 + box.height;
x0 = std::max(x0, clip.x);
y0 = std::max(y0, clip.y);
x1 = std::min(x1, clip.x + clip.width);
y1 = std::min(y1, clip.y + clip.height);
if (x0 < x1 && y0 < y1)
clipStack.push_back(Rect::ltrb(x0, y0, x1, y1));
else
clipStack.push_back(Rect::xywh(0.0, 0.0, 0.0, 0.0));
}
else
{
clipStack.push_back(box);
}
}
void BitmapCanvas::popClip()
{
clipStack.pop_back();
}
void BitmapCanvas::fillRect(const Rect& box, const Colorf& color)
{
drawTile(whiteTexture.get(), (float)((origin.x + box.x) * uiscale), (float)((origin.y + box.y) * uiscale), (float)(box.width * uiscale), (float)(box.height * uiscale), 0.0, 0.0, 1.0, 1.0, color);
}
void BitmapCanvas::drawImage(const std::shared_ptr<Image>& image, const Point& pos)
{
auto& texture = imageTextures[image];
if (!texture)
{
texture = createTexture(image->GetWidth(), image->GetHeight(), image->GetData(), image->GetFormat());
}
Colorf color(1.0f, 1.0f, 1.0f);
drawTile(texture.get(), (float)((origin.x + pos.x) * uiscale), (float)((origin.y + pos.y) * uiscale), (float)(texture->Width * uiscale), (float)(texture->Height * uiscale), 0.0, 0.0, (float)texture->Width, (float)texture->Height, color);
}
void BitmapCanvas::line(const Point& p0, const Point& p1, const Colorf& color)
{
double x0 = origin.x + p0.x;
double y0 = origin.y + p0.y;
double x1 = origin.x + p1.x;
double y1 = origin.y + p1.y;
if (clipStack.empty())// || (clipStack.back().contains({ x0, y0 }) && clipStack.back().contains({ x1, y1 })))
{
drawLineUnclipped({ x0, y0 }, { x1, y1 }, color);
}
else
{
const Rect& clip = clipStack.back();
if (x0 > x1)
{
std::swap(x0, x1);
std::swap(y0, y1);
}
if (x1 < clip.x || x0 >= clip.x + clip.width)
return;
// Clip left edge
if (x0 < clip.x)
{
double dx = x1 - x0;
double dy = y1 - y0;
if (std::abs(dx) < 0.0001)
return;
y0 = y0 + (clip.x - x0) * dy / dx;
x0 = clip.x;
}
// Clip right edge
if (x1 > clip.x + clip.width)
{
double dx = x1 - x0;
double dy = y1 - y0;
if (std::abs(dx) < 0.0001)
return;
y1 = y1 + (clip.x + clip.width - x1) * dy / dx;
x1 = clip.x + clip.width;
}
if (y0 > y1)
{
std::swap(x0, x1);
std::swap(y0, y1);
}
if (y1 < clip.y || y0 >= clip.y + clip.height)
return;
// Clip top edge
if (y0 < clip.y)
{
double dx = x1 - x0;
double dy = y1 - y0;
if (std::abs(dy) < 0.0001)
return;
x0 = x0 + (clip.y - y0) * dx / dy;
y0 = clip.y;
}
// Clip bottom edge
if (y1 > clip.y + clip.height)
{
double dx = x1 - x0;
double dy = y1 - y0;
if (std::abs(dy) < 0.0001)
return;
x1 = x1 + (clip.y + clip.height - y1) * dx / dy;
y1 = clip.y + clip.height;
}
x0 = clamp(x0, clip.x, clip.x + clip.width);
x1 = clamp(x1, clip.x, clip.x + clip.width);
y0 = clamp(y0, clip.y, clip.y + clip.height);
y1 = clamp(y1, clip.y, clip.y + clip.height);
if (x0 != x1 || y0 != y1)
drawLineUnclipped({ x0, y0 }, { x1, y1 }, color);
}
}
void BitmapCanvas::drawText(const Point& pos, const Colorf& color, const std::string& text)
{
double x = std::round((origin.x + pos.x) * uiscale);
double y = std::round((origin.y + pos.y) * uiscale);
UTF8Reader reader(text.data(), text.size());
while (!reader.is_end())
{
CanvasGlyph* glyph = font->getGlyph(reader.character());
if (!glyph->texture)
{
glyph = font->getGlyph(32);
}
if (glyph->texture)
{
double gx = std::round(x + glyph->metrics.leftSideBearing);
double gy = std::round(y + glyph->metrics.yOffset);
drawGlyph(glyph->texture.get(), (float)std::round(gx), (float)std::round(gy), (float)glyph->uvwidth, (float)glyph->uvheight, (float)glyph->u, (float)glyph->v, (float)glyph->uvwidth, (float)glyph->uvheight, color);
}
x += std::round(glyph->metrics.advanceWidth);
reader.next();
}
}
Rect BitmapCanvas::measureText(const std::string& text)
{
double x = 0.0;
double y = font->textmetrics.ascender - font->textmetrics.descender;
UTF8Reader reader(text.data(), text.size());
while (!reader.is_end())
{
CanvasGlyph* glyph = font->getGlyph(reader.character());
if (!glyph->texture)
{
glyph = font->getGlyph(32);
}
x += std::round(glyph->metrics.advanceWidth);
reader.next();
}
return Rect::xywh(0.0, 0.0, x / uiscale, y / uiscale);
}
VerticalTextPosition BitmapCanvas::verticalTextAlign()
{
VerticalTextPosition align;
align.top = 0.0f;
align.baseline = font->textmetrics.ascender / uiscale;
align.bottom = (font->textmetrics.ascender - font->textmetrics.descender) / uiscale;
return align;
}
std::unique_ptr<CanvasTexture> BitmapCanvas::createTexture(int width, int height, const void* pixels, ImageFormat format)
{
auto texture = std::make_unique<CanvasTexture>();
texture->Width = width;
texture->Height = height;
texture->Data.resize(width * height);
if (format == ImageFormat::B8G8R8A8)
{
memcpy(texture->Data.data(), pixels, width * height * sizeof(uint32_t));
}
else
{
const uint32_t* src = (const uint32_t*)pixels;
uint32_t* dest = texture->Data.data();
int count = width * height;
for (int i = 0; i < count; i++)
{
uint32_t a = (src[i] >> 24) & 0xff;
uint32_t b = (src[i] >> 16) & 0xff;
uint32_t g = (src[i] >> 8) & 0xff;
uint32_t r = src[i] & 0xff;
dest[i] = (a << 24) | (r << 16) | (g << 8) | b;
}
}
return texture;
}
void BitmapCanvas::drawLineUnclipped(const Point& p0, const Point& p1, const Colorf& color)
{
if (p0.x == p1.x)
{
drawTile(whiteTexture.get(), (float)((p0.x - 0.5) * uiscale), (float)(p0.y * uiscale), (float)((p1.x + 0.5) * uiscale), (float)(p1.y * uiscale), 0.0f, 0.0f, 1.0f, 1.0f, color);
}
else if (p0.y == p1.y)
{
drawTile(whiteTexture.get(), (float)(p0.x * uiscale), (float)((p0.y - 0.5) * uiscale), (float)(p1.x * uiscale), (float)((p1.y + 0.5) * uiscale), 0.0f, 0.0f, 1.0f, 1.0f, color);
}
else
{
// To do: draw line using bresenham
}
}
int BitmapCanvas::getClipMinX() const
{
return clipStack.empty() ? 0 : (int)std::max(clipStack.back().x * uiscale, 0.0);
}
int BitmapCanvas::getClipMinY() const
{
return clipStack.empty() ? 0 : (int)std::max(clipStack.back().y * uiscale, 0.0);
}
int BitmapCanvas::getClipMaxX() const
{
return clipStack.empty() ? width : (int)std::min((clipStack.back().x + clipStack.back().width) * uiscale, (double)width);
}
int BitmapCanvas::getClipMaxY() const
{
return clipStack.empty() ? height : (int)std::min((clipStack.back().y + clipStack.back().height) * uiscale, (double)height);
}
void BitmapCanvas::drawTile(CanvasTexture* texture, float left, float top, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color)
{
if (width <= 0.0f || height <= 0.0f)
return;
int swidth = texture->Width;
int sheight = texture->Height;
const uint32_t* src = texture->Data.data();
int dwidth = this->width;
int dheight = this->height;
uint32_t* dest = this->pixels.data();
int x0 = (int)left;
int x1 = (int)(left + width);
int y0 = (int)top;
int y1 = (int)(top + height);
x0 = std::max(x0, getClipMinX());
y0 = std::max(y0, getClipMinY());
x1 = std::min(x1, getClipMaxX());
y1 = std::min(y1, getClipMaxY());
if (x1 <= x0 || y1 <= y0)
return;
uint32_t cred = (int32_t)clamp(color.r * 256.0f, 0.0f, 256.0f);
uint32_t cgreen = (int32_t)clamp(color.g * 256.0f, 0.0f, 256.0f);
uint32_t cblue = (int32_t)clamp(color.b * 256.0f, 0.0f, 256.0f);
uint32_t calpha = (int32_t)clamp(color.a * 256.0f, 0.0f, 256.0f);
float uscale = uvwidth / width;
float vscale = uvheight / height;
for (int y = y0; y < y1; y++)
{
float vpix = v + vscale * (y + 0.5f - top);
const uint32_t* sline = src + ((int)vpix) * swidth;
uint32_t* dline = dest + y * dwidth;
for (int x = x0; x < x1; x++)
{
float upix = u + uscale * (x + 0.5f - left);
uint32_t spixel = sline[(int)upix];
uint32_t salpha = spixel >> 24;
uint32_t sred = (spixel >> 16) & 0xff;
uint32_t sgreen = (spixel >> 8) & 0xff;
uint32_t sblue = spixel & 0xff;
uint32_t dpixel = dline[x];
uint32_t dalpha = dpixel >> 24;
uint32_t dred = (dpixel >> 16) & 0xff;
uint32_t dgreen = (dpixel >> 8) & 0xff;
uint32_t dblue = dpixel & 0xff;
// Pixel shade
sred = (cred * sred + 127) >> 8;
sgreen = (cgreen * sgreen + 127) >> 8;
sblue = (cblue * sblue + 127) >> 8;
salpha = (calpha * salpha + 127) >> 8;
// Rescale from [0,255] to [0,256]
uint32_t sa = salpha + (salpha >> 7);
uint32_t sinva = 256 - sa;
// dest.rgba = color.rgba * src.rgba * src.a + dest.rgba * (1-src.a)
uint32_t a = (salpha * sa + dalpha * sinva + 127) >> 8;
uint32_t r = (sred * sa + dred * sinva + 127) >> 8;
uint32_t g = (sgreen * sa + dgreen * sinva + 127) >> 8;
uint32_t b = (sblue * sa + dblue * sinva + 127) >> 8;
dline[x] = (a << 24) | (r << 16) | (g << 8) | b;
}
}
}
void BitmapCanvas::drawGlyph(CanvasTexture* texture, float left, float top, float width, float height, float u, float v, float uvwidth, float uvheight, Colorf color)
{
if (width <= 0.0f || height <= 0.0f)
return;
int swidth = texture->Width;
int sheight = texture->Height;
const uint32_t* src = texture->Data.data();
int dwidth = this->width;
int dheight = this->height;
uint32_t* dest = this->pixels.data();
int x0 = (int)left;
int x1 = (int)(left + width);
int y0 = (int)top;
int y1 = (int)(top + height);
x0 = std::max(x0, getClipMinX());
y0 = std::max(y0, getClipMinY());
x1 = std::min(x1, getClipMaxX());
y1 = std::min(y1, getClipMaxY());
if (x1 <= x0 || y1 <= y0)
return;
uint32_t cred = (int32_t)clamp(color.r * 255.0f, 0.0f, 255.0f);
uint32_t cgreen = (int32_t)clamp(color.g * 255.0f, 0.0f, 255.0f);
uint32_t cblue = (int32_t)clamp(color.b * 255.0f, 0.0f, 255.0f);
float uscale = uvwidth / width;
float vscale = uvheight / height;
for (int y = y0; y < y1; y++)
{
float vpix = v + vscale * (y + 0.5f - top);
const uint32_t* sline = src + ((int)vpix) * swidth;
uint32_t* dline = dest + y * dwidth;
for (int x = x0; x < x1; x++)
{
float upix = u + uscale * (x + 0.5f - left);
uint32_t spixel = sline[(int)upix];
uint32_t sred = (spixel >> 16) & 0xff;
uint32_t sgreen = (spixel >> 8) & 0xff;
uint32_t sblue = spixel & 0xff;
uint32_t dpixel = dline[x];
uint32_t dred = (dpixel >> 16) & 0xff;
uint32_t dgreen = (dpixel >> 8) & 0xff;
uint32_t dblue = dpixel & 0xff;
// Rescale from [0,255] to [0,256]
sred += sred >> 7;
sgreen += sgreen >> 7;
sblue += sblue >> 7;
// dest.rgb = color.rgb * src.rgb + dest.rgb * (1-src.rgb)
uint32_t r = (cred * sred + dred * (256 - sred) + 127) >> 8;
uint32_t g = (cgreen * sgreen + dgreen * (256 - sgreen) + 127) >> 8;
uint32_t b = (cblue * sblue + dblue * (256 - sblue) + 127) >> 8;
dline[x] = 0xff000000 | (r << 16) | (g << 8) | b;
}
}
}
void BitmapCanvas::begin(const Colorf& color)
{
uiscale = window->GetDpiScale();
uint32_t r = (int32_t)clamp(color.r * 255.0f, 0.0f, 255.0f);
uint32_t g = (int32_t)clamp(color.g * 255.0f, 0.0f, 255.0f);
uint32_t b = (int32_t)clamp(color.b * 255.0f, 0.0f, 255.0f);
uint32_t a = (int32_t)clamp(color.a * 255.0f, 0.0f, 255.0f);
uint32_t bgcolor = (a << 24) | (r << 16) | (g << 8) | b;
width = window->GetPixelWidth();
height = window->GetPixelHeight();
pixels.clear();
pixels.resize(width * height, bgcolor);
}
void BitmapCanvas::end()
{
window->PresentBitmap(width, height, pixels.data());
}
void BitmapCanvas::begin3d()
{
}
void BitmapCanvas::end3d()
{
}
/////////////////////////////////////////////////////////////////////////////
std::unique_ptr<Canvas> Canvas::create(DisplayWindow* window)
{
return std::make_unique<BitmapCanvas>(window);
}

View file

@ -0,0 +1,28 @@
#include "core/font.h"
class FontImpl : public Font
{
public:
FontImpl(const std::string& name, double height) : Name(name), Height(height)
{
}
const std::string& GetName() const override
{
return Name;
}
double GetHeight() const override
{
return Height;
}
std::string Name;
double Height = 0.0;
};
std::shared_ptr<Font> Font::Create(const std::string& name, double height)
{
return std::make_shared<FontImpl>(name, height);
}

View file

@ -0,0 +1,43 @@
#include "core/image.h"
#include <cstring>
class ImageImpl : public Image
{
public:
ImageImpl(int width, int height, ImageFormat format, const void* data) : Width(width), Height(height), Format(format)
{
Data = std::make_unique<uint32_t[]>(width * height);
memcpy(Data.get(), data, width * height * sizeof(uint32_t));
}
int GetWidth() const override
{
return Width;
}
int GetHeight() const override
{
return Height;
}
ImageFormat GetFormat() const override
{
return Format;
}
void* GetData() const override
{
return Data.get();
}
int Width = 0;
int Height = 0;
ImageFormat Format = {};
std::unique_ptr<uint32_t[]> Data;
};
std::shared_ptr<Image> Image::Create(int width, int height, ImageFormat format, const void* data)
{
return std::make_shared<ImageImpl>(width, height, format, data);
}

View file

@ -0,0 +1,16 @@
ISC License
© 2019-2022 Thomas Oltmann and contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,95 @@
/* This file is part of libschrift.
*
* © 2019-2022 Thomas Oltmann and contributors
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
#ifndef SCHRIFT_H
#define SCHRIFT_H 1
#include <stddef.h> /* size_t */
#include <stdint.h> /* uint_fast32_t, uint_least32_t */
#ifdef __cplusplus
extern "C" {
#endif
#define SFT_DOWNWARD_Y 0x01
typedef struct SFT SFT;
typedef struct SFT_Font SFT_Font;
typedef uint_least32_t SFT_UChar; /* Guaranteed to be compatible with char32_t. */
typedef uint_fast32_t SFT_Glyph;
typedef struct SFT_LMetrics SFT_LMetrics;
typedef struct SFT_GMetrics SFT_GMetrics;
typedef struct SFT_Kerning SFT_Kerning;
typedef struct SFT_Image SFT_Image;
struct SFT
{
SFT_Font *font;
double xScale;
double yScale;
double xOffset;
double yOffset;
int flags;
};
struct SFT_LMetrics
{
double ascender;
double descender;
double lineGap;
};
struct SFT_GMetrics
{
double advanceWidth;
double leftSideBearing;
int yOffset;
int minWidth;
int minHeight;
};
struct SFT_Kerning
{
double xShift;
double yShift;
};
struct SFT_Image
{
void *pixels;
int width;
int height;
};
const char *sft_version(void);
SFT_Font *sft_loadmem (const void *mem, size_t size);
SFT_Font *sft_loadfile(const char *filename);
void sft_freefont(SFT_Font *font);
int sft_lmetrics(const SFT *sft, SFT_LMetrics *metrics);
int sft_lookup (const SFT *sft, SFT_UChar codepoint, SFT_Glyph *glyph);
int sft_gmetrics(const SFT *sft, SFT_Glyph glyph, SFT_GMetrics *metrics);
int sft_kerning (const SFT *sft, SFT_Glyph leftGlyph, SFT_Glyph rightGlyph,
SFT_Kerning *kerning);
int sft_render (const SFT *sft, SFT_Glyph glyph, SFT_Image image);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,883 @@
#include "core/span_layout.h"
#include "core/canvas.h"
#include "core/widget.h"
#include "core/font.h"
#include "core/image.h"
SpanLayout::SpanLayout()
{
}
SpanLayout::~SpanLayout()
{
}
void SpanLayout::Clear()
{
objects.clear();
text.clear();
lines.clear();
}
std::vector<Rect> SpanLayout::GetRectById(int id) const
{
std::vector<Rect> segment_rects;
double x = position.x;
double y = position.y;
for (std::vector<Line>::size_type line_index = 0; line_index < lines.size(); line_index++)
{
const Line& line = lines[line_index];
for (std::vector<LineSegment>::size_type segment_index = 0; segment_index < line.segments.size(); segment_index++)
{
const LineSegment& segment = line.segments[segment_index];
if (segment.id == id)
{
segment_rects.push_back(Rect(x + segment.x_position, y, segment.width, y + line.height));
}
}
y += line.height;
}
return segment_rects;
}
void SpanLayout::DrawLayout(Canvas* canvas)
{
double x = position.x;
double y = position.y;
for (std::vector<Line>::size_type line_index = 0; line_index < lines.size(); line_index++)
{
Line& line = lines[line_index];
for (std::vector<LineSegment>::size_type segment_index = 0; segment_index < line.segments.size(); segment_index++)
{
LineSegment& segment = line.segments[segment_index];
switch (segment.type)
{
case object_text:
DrawLayoutText(canvas, line, segment, x, y);
break;
case object_image:
DrawLayoutImage(canvas, line, segment, x, y);
break;
case object_component:
break;
}
}
if (line_index + 1 == lines.size() && !line.segments.empty())
{
LineSegment& segment = line.segments.back();
if (cursor_visible && segment.end <= cursor_pos)
{
switch (segment.type)
{
case object_text:
{
double cursor_x = x + segment.x_position + canvas->measureText(segment.font, text.substr(segment.start, segment.end - segment.start)).width;
double cursor_width = 1;
canvas->fillRect(Rect::ltrb(cursor_x, y + line.ascender - segment.ascender, cursor_width, y + line.ascender + segment.descender), cursor_color);
}
break;
}
}
}
y += line.height;
}
}
void SpanLayout::DrawLayoutEllipsis(Canvas* canvas, const Rect& content_rect)
{
is_ellipsis_draw = true;
ellipsis_content_rect = content_rect;
try
{
is_ellipsis_draw = false;
DrawLayout(canvas);
}
catch (...)
{
is_ellipsis_draw = false;
throw;
}
}
void SpanLayout::DrawLayoutImage(Canvas* canvas, Line& line, LineSegment& segment, double x, double y)
{
canvas->drawImage(segment.image, Point(x + segment.x_position, y + line.ascender - segment.ascender));
}
void SpanLayout::DrawLayoutText(Canvas* canvas, Line& line, LineSegment& segment, double x, double y)
{
std::string segment_text = text.substr(segment.start, segment.end - segment.start);
int length = (int)segment_text.length();
int s1 = clamp((int)sel_start - (int)segment.start, 0, length);
int s2 = clamp((int)sel_end - (int)segment.start, 0, length);
if (s1 != s2)
{
double xx = x + segment.x_position;
double xx0 = xx + canvas->measureText(segment.font, segment_text.substr(0, s1)).width;
double xx1 = xx0 + canvas->measureText(segment.font, segment_text.substr(s1, s2 - s1)).width;
double sel_width = canvas->measureText(segment.font, segment_text.substr(s1, s2 - s1)).width;
canvas->fillRect(Rect::ltrb(xx0, y + line.ascender - segment.ascender, xx1, y + line.ascender + segment.descender), sel_background);
if (cursor_visible && cursor_pos >= segment.start && cursor_pos < segment.end)
{
double cursor_x = x + segment.x_position + canvas->measureText(segment.font, text.substr(segment.start, cursor_pos - segment.start)).width;
double cursor_width = cursor_overwrite_mode ? canvas->measureText(segment.font, text.substr(cursor_pos, 1)).width : 1;
canvas->fillRect(Rect::ltrb(cursor_x, y + line.ascender - segment.ascender, cursor_x + cursor_width, y + line.ascender + segment.descender), cursor_color);
}
if (s1 > 0)
{
if (is_ellipsis_draw)
canvas->drawTextEllipsis(segment.font, Point(xx, y + line.ascender), ellipsis_content_rect, segment_text.substr(0, s1), segment.color);
else
canvas->drawText(segment.font, Point(xx, y + line.ascender), segment_text.substr(0, s1), segment.color);
}
if (is_ellipsis_draw)
canvas->drawTextEllipsis(segment.font, Point(xx0, y + line.ascender), ellipsis_content_rect, segment_text.substr(s1, s2 - s1), sel_foreground);
else
canvas->drawText(segment.font, Point(xx0, y + line.ascender), segment_text.substr(s1, s2 - s1), sel_foreground);
xx += sel_width;
if (s2 < length)
{
if (is_ellipsis_draw)
canvas->drawTextEllipsis(segment.font, Point(xx1, y + line.ascender), ellipsis_content_rect, segment_text.substr(s2), segment.color);
else
canvas->drawText(segment.font, Point(xx1, y + line.ascender), segment_text.substr(s2), segment.color);
}
}
else
{
if (cursor_visible && cursor_pos >= segment.start && cursor_pos < segment.end)
{
double cursor_x = x + segment.x_position + canvas->measureText(segment.font, text.substr(segment.start, cursor_pos - segment.start)).width;
double cursor_width = cursor_overwrite_mode ? canvas->measureText(segment.font, text.substr(cursor_pos, 1)).width : 1;
canvas->fillRect(Rect::ltrb(cursor_x, y + line.ascender - segment.ascender, cursor_x + cursor_width, y + line.ascender + segment.descender), cursor_color);
}
if (is_ellipsis_draw)
canvas->drawTextEllipsis(segment.font, Point(x + segment.x_position, y + line.ascender), ellipsis_content_rect, segment_text, segment.color);
else
canvas->drawText(segment.font, Point(x + segment.x_position, y + line.ascender), segment_text, segment.color);
}
}
SpanLayout::HitTestResult SpanLayout::HitTest(Canvas* canvas, const Point& pos)
{
SpanLayout::HitTestResult result;
if (lines.empty())
{
result.type = SpanLayout::HitTestResult::no_objects_available;
return result;
}
double x = position.x;
double y = position.y;
// Check if we are outside to the top
if (pos.y < y)
{
result.type = SpanLayout::HitTestResult::outside_top;
result.object_id = lines[0].segments[0].id;
result.offset = 0;
return result;
}
for (std::vector<Line>::size_type line_index = 0; line_index < lines.size(); line_index++)
{
Line& line = lines[line_index];
// Check if we found current line
if (pos.y >= y && pos.y <= y + line.height)
{
for (std::vector<LineSegment>::size_type segment_index = 0; segment_index < line.segments.size(); segment_index++)
{
LineSegment& segment = line.segments[segment_index];
// Check if we are outside to the left
if (segment_index == 0 && pos.x < x)
{
result.type = SpanLayout::HitTestResult::outside_left;
result.object_id = segment.id;
result.offset = segment.start;
return result;
}
// Check if we are inside a segment
if (pos.x >= x + segment.x_position && pos.x <= x + segment.x_position + segment.width)
{
std::string segment_text = text.substr(segment.start, segment.end - segment.start);
Point hit_point(pos.x - x - segment.x_position, 0);
size_t offset = segment.start + canvas->getCharacterIndex(segment.font, segment_text, hit_point);
result.type = SpanLayout::HitTestResult::inside;
result.object_id = segment.id;
result.offset = offset;
return result;
}
// Check if we are outside to the right
if (segment_index == line.segments.size() - 1 && pos.x > x + segment.x_position + segment.width)
{
result.type = SpanLayout::HitTestResult::outside_right;
result.object_id = segment.id;
result.offset = segment.end;
return result;
}
}
}
y += line.height;
}
// We are outside to the bottom
const Line& last_line = lines[lines.size() - 1];
const LineSegment& last_segment = last_line.segments[last_line.segments.size() - 1];
result.type = SpanLayout::HitTestResult::outside_bottom;
result.object_id = last_segment.id;
result.offset = last_segment.end;
return result;
}
Size SpanLayout::GetSize() const
{
return GetRect().size();
}
Rect SpanLayout::GetRect() const
{
double x = position.x;
double y = position.y;
const double max_value = 0x70000000;
double left = max_value;
double top = max_value;
double right = -max_value;
double bottom = -max_value;
for (std::vector<Line>::size_type line_index = 0; line_index < lines.size(); line_index++)
{
const Line& line = lines[line_index];
for (std::vector<LineSegment>::size_type segment_index = 0; segment_index < line.segments.size(); segment_index++)
{
const LineSegment& segment = line.segments[segment_index];
Rect area(Point(x + segment.x_position, y), Size(segment.width, line.height));
left = std::min(left, area.left());
right = std::max(right, area.right());
top = std::min(top, area.top());
bottom = std::max(bottom, area.bottom());
}
y += line.height;
}
if (left > right)
left = right = position.x;
if (top > bottom)
top = bottom = position.y;
return Rect::ltrb(left, top, right, bottom);
}
void SpanLayout::AddText(const std::string& more_text, std::shared_ptr<Font> font, const Colorf& color, int id)
{
SpanObject object;
object.type = object_text;
object.start = text.length();
object.end = object.start + more_text.length();
object.font = font;
object.color = color;
object.id = id;
objects.push_back(object);
text += more_text;
}
void SpanLayout::AddImage(std::shared_ptr<Image> image, double baseline_offset, int id)
{
SpanObject object;
object.type = object_image;
object.image = image;
object.baseline_offset = baseline_offset;
object.id = id;
object.start = text.length();
object.end = object.start + 1;
objects.push_back(object);
text += "*";
}
void SpanLayout::AddWidget(Widget* component, double baseline_offset, int id)
{
SpanObject object;
object.type = object_component;
object.component = component;
object.baseline_offset = baseline_offset;
object.id = id;
object.start = text.length();
object.end = object.start + 1;
objects.push_back(object);
text += "*";
}
void SpanLayout::Layout(Canvas* canvas, double max_width)
{
LayoutLines(canvas, max_width);
switch (alignment)
{
case span_right: AlignRight(max_width); break;
case span_center: AlignCenter(max_width); break;
case span_justify: AlignJustify(max_width); break;
case span_left:
default: break;
}
}
void SpanLayout::SetPosition(const Point& pos)
{
position = pos;
}
SpanLayout::TextSizeResult SpanLayout::FindTextSize(Canvas* canvas, const TextBlock& block, size_t object_index)
{
std::shared_ptr<Font> font = objects[object_index].font;
if (layout_cache.object_index != (int)object_index)
{
layout_cache.object_index = (int)object_index;
layout_cache.metrics = canvas->getFontMetrics(font);
}
TextSizeResult result;
result.start = block.start;
size_t pos = block.start;
double x_position = 0;
while (pos != block.end)
{
size_t end = std::min(objects[object_index].end, block.end);
std::string subtext = text.substr(pos, end - pos);
Size text_size = canvas->measureText(font, subtext).size();
result.width += text_size.width;
result.height = std::max(result.height, layout_cache.metrics.height + layout_cache.metrics.external_leading);
result.ascender = std::max(result.ascender, layout_cache.metrics.ascent);
result.descender = std::max(result.descender, layout_cache.metrics.descent);
LineSegment segment;
segment.type = object_text;
segment.start = pos;
segment.end = end;
segment.font = objects[object_index].font;
segment.color = objects[object_index].color;
segment.id = objects[object_index].id;
segment.x_position = x_position;
segment.width = text_size.width;
segment.ascender = layout_cache.metrics.ascent;
segment.descender = layout_cache.metrics.descent;
x_position += text_size.width;
result.segments.push_back(segment);
pos = end;
if (pos == objects[object_index].end)
{
object_index++;
result.objects_traversed++;
if (object_index < objects.size())
{
layout_cache.object_index = (int)object_index;
font = objects[object_index].font;
layout_cache.metrics = canvas->getFontMetrics(font);
}
}
}
result.end = pos;
return result;
}
std::vector<SpanLayout::TextBlock> SpanLayout::FindTextBlocks()
{
std::vector<TextBlock> blocks;
std::vector<SpanObject>::iterator block_object_it;
// Find first object that is not text:
for (block_object_it = objects.begin(); block_object_it != objects.end() && (*block_object_it).type == object_text; ++block_object_it);
std::string::size_type pos = 0;
while (pos < text.size())
{
// Find end of text block:
std::string::size_type end_pos;
switch (text[pos])
{
case ' ':
case '\t':
case '\n':
end_pos = text.find_first_not_of(text[pos], pos);
break;
default:
end_pos = text.find_first_of(" \t\n", pos);
break;
}
if (end_pos == std::string::npos)
end_pos = text.length();
// If we traversed past an object that is not text:
if (block_object_it != objects.end() && (*block_object_it).start < end_pos)
{
// End text block
end_pos = (*block_object_it).start;
if (end_pos > pos)
{
TextBlock block;
block.start = pos;
block.end = end_pos;
blocks.push_back(block);
}
// Create object block:
pos = end_pos;
end_pos = pos + 1;
TextBlock block;
block.start = pos;
block.end = end_pos;
blocks.push_back(block);
// Find next object that is not text:
for (++block_object_it; block_object_it != objects.end() && (*block_object_it).type == object_text; ++block_object_it);
}
else
{
if (end_pos > pos)
{
TextBlock block;
block.start = pos;
block.end = end_pos;
blocks.push_back(block);
}
}
pos = end_pos;
}
return blocks;
}
void SpanLayout::SetAlign(SpanAlign align)
{
alignment = align;
}
void SpanLayout::LayoutLines(Canvas* canvas, double max_width)
{
lines.clear();
if (objects.empty())
return;
layout_cache.metrics = {};
layout_cache.object_index = -1;
CurrentLine current_line;
std::vector<TextBlock> blocks = FindTextBlocks();
for (std::vector<TextBlock>::size_type block_index = 0; block_index < blocks.size(); block_index++)
{
if (objects[current_line.object_index].type == object_text)
LayoutText(canvas, blocks, block_index, current_line, max_width);
else
LayoutBlock(current_line, max_width, blocks, block_index);
}
NextLine(current_line);
}
void SpanLayout::LayoutBlock(CurrentLine& current_line, double max_width, std::vector<TextBlock>& blocks, std::vector<TextBlock>::size_type block_index)
{
if (objects[current_line.object_index].float_type == float_none)
LayoutInlineBlock(current_line, max_width, blocks, block_index);
else
LayoutFloatBlock(current_line, max_width);
current_line.object_index++;
}
void SpanLayout::LayoutInlineBlock(CurrentLine& current_line, double max_width, std::vector<TextBlock>& blocks, std::vector<TextBlock>::size_type block_index)
{
Size size;
LineSegment segment;
if (objects[current_line.object_index].type == object_image)
{
size = Size(objects[current_line.object_index].image->GetWidth(), objects[current_line.object_index].image->GetHeight());
segment.type = object_image;
segment.image = objects[current_line.object_index].image;
}
else if (objects[current_line.object_index].type == object_component)
{
size = objects[current_line.object_index].component->GetSize();
segment.type = object_component;
segment.component = objects[current_line.object_index].component;
}
if (current_line.x_position + size.width > max_width)
NextLine(current_line);
segment.x_position = current_line.x_position;
segment.width = size.width;
segment.start = blocks[block_index].start;
segment.end = blocks[block_index].end;
segment.id = objects[current_line.object_index].id;
segment.ascender = size.height - objects[current_line.object_index].baseline_offset;
current_line.cur_line.segments.push_back(segment);
current_line.cur_line.height = std::max(current_line.cur_line.height, size.height + objects[current_line.object_index].baseline_offset);
current_line.cur_line.ascender = std::max(current_line.cur_line.ascender, segment.ascender);
current_line.x_position += size.width;
}
void SpanLayout::LayoutFloatBlock(CurrentLine& current_line, double max_width)
{
FloatBox floatbox;
floatbox.type = objects[current_line.object_index].type;
floatbox.image = objects[current_line.object_index].image;
floatbox.component = objects[current_line.object_index].component;
floatbox.id = objects[current_line.object_index].id;
if (objects[current_line.object_index].type == object_image)
floatbox.rect = Rect::xywh(0, current_line.y_position, floatbox.image->GetWidth(), floatbox.image->GetHeight());
else if (objects[current_line.object_index].type == object_component)
floatbox.rect = Rect::xywh(0, current_line.y_position, floatbox.component->GetWidth(), floatbox.component->GetHeight());
if (objects[current_line.object_index].float_type == float_left)
floats_left.push_back(FloatBoxLeft(floatbox, max_width));
else
floats_right.push_back(FloatBoxRight(floatbox, max_width));
ReflowLine(current_line, max_width);
}
void SpanLayout::ReflowLine(CurrentLine& step, double max_width)
{
}
SpanLayout::FloatBox SpanLayout::FloatBoxLeft(FloatBox box, double max_width)
{
return FloatBoxAny(box, max_width, floats_left);
}
SpanLayout::FloatBox SpanLayout::FloatBoxRight(FloatBox box, double max_width)
{
return FloatBoxAny(box, max_width, floats_right);
}
SpanLayout::FloatBox SpanLayout::FloatBoxAny(FloatBox box, double max_width, const std::vector<FloatBox>& floats1)
{
bool restart;
do
{
restart = false;
for (size_t i = 0; i < floats1.size(); i++)
{
double top = std::max(floats1[i].rect.top(), box.rect.top());
double bottom = std::min(floats1[i].rect.bottom(), box.rect.bottom());
if (bottom > top && box.rect.left() < floats1[i].rect.right())
{
Size s = box.rect.size();
box.rect.x = floats1[i].rect.x;
box.rect.width = s.width;
if (!BoxFitsOnLine(box, max_width))
{
box.rect.x = 0;
box.rect.width = s.width;
box.rect.y = floats1[i].rect.bottom();
box.rect.height = s.height;
restart = true;
break;
}
}
}
} while (restart);
return box;
}
bool SpanLayout::BoxFitsOnLine(const FloatBox& box, double max_width)
{
for (size_t i = 0; i < floats_right.size(); i++)
{
double top = std::max(floats_right[i].rect.top(), box.rect.top());
double bottom = std::min(floats_right[i].rect.bottom(), box.rect.bottom());
if (bottom > top)
{
if (box.rect.right() + floats_right[i].rect.right() > max_width)
return false;
}
}
return true;
}
void SpanLayout::LayoutText(Canvas* canvas, std::vector<TextBlock> blocks, std::vector<TextBlock>::size_type block_index, CurrentLine& current_line, double max_width)
{
TextSizeResult text_size_result = FindTextSize(canvas, blocks[block_index], current_line.object_index);
current_line.object_index += text_size_result.objects_traversed;
current_line.cur_line.width = current_line.x_position;
if (IsNewline(blocks[block_index]))
{
current_line.cur_line.height = std::max(current_line.cur_line.height, text_size_result.height);
current_line.cur_line.ascender = std::max(current_line.cur_line.ascender, text_size_result.ascender);
NextLine(current_line);
}
else
{
if (!FitsOnLine(current_line.x_position, text_size_result, max_width) && !IsWhitespace(blocks[block_index]))
{
if (LargerThanLine(text_size_result, max_width))
{
// force line breaks to make it fit
ForcePlaceLineSegments(current_line, text_size_result, max_width);
}
else
{
NextLine(current_line);
PlaceLineSegments(current_line, text_size_result);
}
}
else
{
PlaceLineSegments(current_line, text_size_result);
}
}
}
void SpanLayout::NextLine(CurrentLine& current_line)
{
current_line.cur_line.width = current_line.x_position;
for (std::vector<LineSegment>::reverse_iterator it = current_line.cur_line.segments.rbegin(); it != current_line.cur_line.segments.rend(); ++it)
{
LineSegment& segment = *it;
if (segment.type == object_text)
{
std::string s = text.substr(segment.start, segment.end - segment.start);
if (s.find_first_not_of(" \t\r\n") != std::string::npos)
{
current_line.cur_line.width = segment.x_position + segment.width;
break;
}
else
{
// We remove the width so that GetRect() reports the correct sizes
segment.width = 0;
}
}
else
{
current_line.cur_line.width = segment.x_position + segment.width;
break;
}
}
double height = current_line.cur_line.height;
lines.push_back(current_line.cur_line);
current_line.cur_line = Line();
current_line.x_position = 0;
current_line.y_position += height;
}
void SpanLayout::PlaceLineSegments(CurrentLine& current_line, TextSizeResult& text_size_result)
{
for (std::vector<LineSegment>::iterator it = text_size_result.segments.begin(); it != text_size_result.segments.end(); ++it)
{
LineSegment segment = *it;
segment.x_position += current_line.x_position;
current_line.cur_line.segments.push_back(segment);
}
current_line.x_position += text_size_result.width;
current_line.cur_line.height = std::max(current_line.cur_line.height, text_size_result.height);
current_line.cur_line.ascender = std::max(current_line.cur_line.ascender, text_size_result.ascender);
}
void SpanLayout::ForcePlaceLineSegments(CurrentLine& current_line, TextSizeResult& text_size_result, double max_width)
{
if (current_line.x_position != 0)
NextLine(current_line);
// to do: do this properly - for now we just place the entire block on one line
PlaceLineSegments(current_line, text_size_result);
}
bool SpanLayout::IsNewline(const TextBlock& block)
{
return block.start != block.end && text[block.start] == '\n';
}
bool SpanLayout::IsWhitespace(const TextBlock& block)
{
return block.start != block.end && text[block.start] == ' ';
}
bool SpanLayout::FitsOnLine(double x_position, const TextSizeResult& text_size_result, double max_width)
{
return x_position + text_size_result.width <= max_width;
}
bool SpanLayout::LargerThanLine(const TextSizeResult& text_size_result, double max_width)
{
return text_size_result.width > max_width;
}
void SpanLayout::AlignRight(double max_width)
{
for (std::vector<Line>::size_type line_index = 0; line_index < lines.size(); line_index++)
{
Line& line = lines[line_index];
double offset = max_width - line.width;
if (offset < 0) offset = 0;
for (std::vector<LineSegment>::size_type segment_index = 0; segment_index < line.segments.size(); segment_index++)
{
LineSegment& segment = line.segments[segment_index];
segment.x_position += offset;
}
}
}
void SpanLayout::AlignCenter(double max_width)
{
for (std::vector<Line>::size_type line_index = 0; line_index < lines.size(); line_index++)
{
Line& line = lines[line_index];
double offset = (max_width - line.width) / 2;
if (offset < 0) offset = 0;
for (std::vector<LineSegment>::size_type segment_index = 0; segment_index < line.segments.size(); segment_index++)
{
LineSegment& segment = line.segments[segment_index];
segment.x_position += offset;
}
}
}
void SpanLayout::AlignJustify(double max_width)
{
// Note, we do not justify the last line
for (std::vector<Line>::size_type line_index = 0; line_index + 1 < lines.size(); line_index++)
{
Line& line = lines[line_index];
double offset = max_width - line.width;
if (offset < 0) offset = 0;
if (line.segments.size() <= 1) // Do not justify line if only one word exists
continue;
for (std::vector<LineSegment>::size_type segment_index = 0; segment_index < line.segments.size(); segment_index++)
{
LineSegment& segment = line.segments[segment_index];
segment.x_position += (offset * segment_index) / (line.segments.size() - 1);
}
}
}
Size SpanLayout::FindPreferredSize(Canvas* canvas)
{
LayoutLines(canvas, 0x70000000); // Feed it with a very long length so it ends up on one line
return GetRect().size();
}
void SpanLayout::SetSelectionRange(std::string::size_type start, std::string::size_type end)
{
sel_start = start;
sel_end = end;
if (sel_end < sel_start)
sel_end = sel_start;
}
void SpanLayout::SetSelectionColors(const Colorf& foreground, const Colorf& background)
{
sel_foreground = foreground;
sel_background = background;
}
void SpanLayout::ShowCursor()
{
cursor_visible = true;
}
void SpanLayout::HideCursor()
{
cursor_visible = false;
}
void SpanLayout::SetCursorPos(std::string::size_type pos)
{
cursor_pos = pos;
}
void SpanLayout::SetCursorOverwriteMode(bool enable)
{
cursor_overwrite_mode = enable;
}
void SpanLayout::SetCursorColor(const Colorf& color)
{
cursor_color = color;
}
std::string SpanLayout::GetCombinedText() const
{
return text;
}
void SpanLayout::SetComponentGeometry()
{
double x = position.x;
double y = position.y;
for (size_t i = 0; i < lines.size(); i++)
{
for (size_t j = 0; j < lines[i].segments.size(); j++)
{
if (lines[i].segments[j].type == object_component)
{
Point pos(x + lines[i].segments[j].x_position, y + lines[i].ascender - lines[i].segments[j].ascender);
Size size = lines[i].segments[j].component->GetSize();
Rect rect(pos, size);
lines[i].segments[j].component->SetFrameGeometry(rect);
}
}
y += lines[i].height;
}
}
double SpanLayout::GetFirstBaselineOffset()
{
if (!lines.empty())
{
return lines.front().ascender;
}
else
{
return 0;
}
}
double SpanLayout::GetLastBaselineOffset()
{
if (!lines.empty())
{
double y = 0;
for (size_t i = 0; i + 1 < lines.size(); i++)
y += lines[i].height;
return y + lines.back().ascender;
}
else
{
return 0;
}
}

View file

@ -0,0 +1,29 @@
#include "core/timer.h"
#include "core/widget.h"
Timer::Timer(Widget* owner) : OwnerObj(owner)
{
PrevTimerObj = owner->FirstTimerObj;
if (PrevTimerObj)
PrevTimerObj->PrevTimerObj = this;
owner->FirstTimerObj = this;
}
Timer::~Timer()
{
if (PrevTimerObj)
PrevTimerObj->NextTimerObj = NextTimerObj;
if (NextTimerObj)
NextTimerObj->PrevTimerObj = PrevTimerObj;
if (OwnerObj->FirstTimerObj == this)
OwnerObj->FirstTimerObj = NextTimerObj;
}
void Timer::Start(int timeoutMilliseconds, bool repeat)
{
}
void Timer::Stop()
{
}

View file

@ -0,0 +1,153 @@
/*
** 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.
**
*/
#include "core/utf8reader.h"
namespace
{
static const char trailing_bytes_for_utf8[256] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5
};
static const unsigned char bitmask_leadbyte_for_utf8[6] =
{
0x7f,
0x1f,
0x0f,
0x07,
0x03,
0x01
};
}
UTF8Reader::UTF8Reader(const std::string::value_type *text, std::string::size_type length) : length(length), data((unsigned char *)text)
{
}
bool UTF8Reader::is_end()
{
return current_position >= length;
}
unsigned int UTF8Reader::character()
{
if (current_position >= length)
return 0;
int trailing_bytes = trailing_bytes_for_utf8[data[current_position]];
if (trailing_bytes == 0 && (data[current_position] & 0x80) == 0x80)
return '?';
if (current_position + 1 + trailing_bytes > length)
{
return '?';
}
else
{
unsigned int ucs4 = (data[current_position] & bitmask_leadbyte_for_utf8[trailing_bytes]);
for (std::string::size_type i = 0; i < trailing_bytes; i++)
{
if ((data[current_position + 1 + i] & 0xC0) == 0x80)
ucs4 = (ucs4 << 6) + (data[current_position + 1 + i] & 0x3f);
else
return '?';
}
// To do: verify that the ucs4 value is in the range for the trailing_bytes specified in the lead byte.
return ucs4;
}
}
std::string::size_type UTF8Reader::char_length()
{
if (current_position < length)
{
int trailing_bytes = trailing_bytes_for_utf8[data[current_position]];
if (current_position + 1 + trailing_bytes > length)
return 1;
for (std::string::size_type i = 0; i < trailing_bytes; i++)
{
if ((data[current_position + 1 + i] & 0xC0) != 0x80)
return 1;
}
return 1 + trailing_bytes;
}
else
{
return 0;
}
}
void UTF8Reader::prev()
{
if (current_position > length)
current_position = length;
if (current_position > 0)
{
current_position--;
move_to_leadbyte();
}
}
void UTF8Reader::next()
{
current_position += char_length();
}
void UTF8Reader::move_to_leadbyte()
{
if (current_position < length)
{
int lead_position = (int)current_position;
while (lead_position > 0 && (data[lead_position] & 0xC0) == 0x80)
lead_position--;
int trailing_bytes = trailing_bytes_for_utf8[data[lead_position]];
if (lead_position + trailing_bytes >= current_position)
current_position = lead_position;
}
}
std::string::size_type UTF8Reader::position()
{
return current_position;
}
void UTF8Reader::set_position(std::string::size_type position)
{
current_position = position;
}

View file

@ -0,0 +1,594 @@
#include "core/widget.h"
#include "core/timer.h"
#include "core/colorf.h"
#include <stdexcept>
Widget::Widget(Widget* parent, WidgetType type) : Type(type)
{
if (type != WidgetType::Child)
{
DispWindow = DisplayWindow::Create(this);
DispCanvas = Canvas::create(DispWindow.get());
}
SetParent(parent);
}
Widget::~Widget()
{
while (LastChildObj)
delete LastChildObj;
while (FirstTimerObj)
delete FirstTimerObj;
DetachFromParent();
}
void Widget::SetParent(Widget* newParent)
{
if (ParentObj != newParent)
{
if (ParentObj)
DetachFromParent();
if (newParent)
{
PrevSiblingObj = newParent->LastChildObj;
if (PrevSiblingObj) PrevSiblingObj->NextSiblingObj = this;
newParent->LastChildObj = this;
if (!newParent->FirstChildObj) newParent->FirstChildObj = this;
ParentObj = newParent;
}
}
}
void Widget::MoveBefore(Widget* sibling)
{
if (sibling && sibling->ParentObj != ParentObj) throw std::runtime_error("Invalid sibling passed to Widget.MoveBefore");
if (!ParentObj) throw std::runtime_error("Widget must have a parent before it can be moved");
if (NextSiblingObj != sibling)
{
Widget* p = ParentObj;
DetachFromParent();
ParentObj = p;
if (sibling)
{
NextSiblingObj = sibling;
PrevSiblingObj = sibling->PrevSiblingObj;
sibling->PrevSiblingObj = this;
if (PrevSiblingObj) PrevSiblingObj->NextSiblingObj = this;
if (ParentObj->FirstChildObj == sibling) ParentObj->FirstChildObj = this;
}
else
{
PrevSiblingObj = ParentObj->LastChildObj;
if (PrevSiblingObj) PrevSiblingObj->NextSiblingObj = this;
ParentObj->LastChildObj = this;
if (!ParentObj->FirstChildObj) ParentObj->FirstChildObj = this;
}
}
}
void Widget::DetachFromParent()
{
if (PrevSiblingObj)
PrevSiblingObj->NextSiblingObj = NextSiblingObj;
if (NextSiblingObj)
NextSiblingObj->PrevSiblingObj = PrevSiblingObj;
if (ParentObj)
{
if (ParentObj->FirstChildObj == this)
ParentObj->FirstChildObj = NextSiblingObj;
if (ParentObj->LastChildObj == this)
ParentObj->LastChildObj = PrevSiblingObj;
}
PrevSiblingObj = nullptr;
NextSiblingObj = nullptr;
ParentObj = nullptr;
}
std::string Widget::GetWindowTitle() const
{
return WindowTitle;
}
void Widget::SetWindowTitle(const std::string& text)
{
if (WindowTitle != text)
{
WindowTitle = text;
if (DispWindow)
DispWindow->SetWindowTitle(WindowTitle);
}
}
Size Widget::GetSize() const
{
return ContentGeometry.size();
}
Rect Widget::GetFrameGeometry() const
{
if (Type == WidgetType::Child)
{
return FrameGeometry;
}
else
{
return DispWindow->GetWindowFrame();
}
}
void Widget::SetNoncontentSizes(double left, double top, double right, double bottom)
{
Noncontent.Left = left;
Noncontent.Top = top;
Noncontent.Right = right;
Noncontent.Bottom = bottom;
}
void Widget::SetFrameGeometry(const Rect& geometry)
{
if (Type == WidgetType::Child)
{
FrameGeometry = geometry;
double left = FrameGeometry.left() + Noncontent.Left;
double top = FrameGeometry.top() + Noncontent.Top;
double right = FrameGeometry.right() - Noncontent.Right;
double bottom = FrameGeometry.bottom() - Noncontent.Bottom;
left = std::min(left, FrameGeometry.right());
top = std::min(top, FrameGeometry.right());
right = std::max(right, FrameGeometry.left());
bottom = std::max(bottom, FrameGeometry.top());
ContentGeometry = Rect::ltrb(left, top, right, bottom);
OnGeometryChanged();
}
else
{
DispWindow->SetWindowFrame(geometry);
}
}
void Widget::Show()
{
if (Type != WidgetType::Child)
{
DispWindow->Show();
}
}
void Widget::ShowFullscreen()
{
if (Type != WidgetType::Child)
{
DispWindow->ShowFullscreen();
}
}
void Widget::ShowMaximized()
{
if (Type != WidgetType::Child)
{
DispWindow->ShowMaximized();
}
}
void Widget::ShowMinimized()
{
if (Type != WidgetType::Child)
{
DispWindow->ShowMinimized();
}
}
void Widget::ShowNormal()
{
if (Type != WidgetType::Child)
{
DispWindow->ShowNormal();
}
}
void Widget::Hide()
{
if (Type != WidgetType::Child)
{
if (DispWindow)
DispWindow->Hide();
}
}
void Widget::ActivateWindow()
{
if (Type != WidgetType::Child)
{
DispWindow->Activate();
}
}
void Widget::Close()
{
OnClose();
}
void Widget::SetWindowBackground(const Colorf& color)
{
Widget* w = Window();
if (w && w->WindowBackground != color)
{
w->WindowBackground = color;
Update();
}
}
void Widget::SetWindowBorderColor(const Colorf& color)
{
Widget* w = Window();
if (w)
{
w->DispWindow->SetBorderColor(color.toBgra8());
w->DispWindow->Update();
}
}
void Widget::SetWindowCaptionColor(const Colorf& color)
{
Widget* w = Window();
if (w)
{
w->DispWindow->SetCaptionColor(color.toBgra8());
w->DispWindow->Update();
}
}
void Widget::SetWindowCaptionTextColor(const Colorf& color)
{
Widget* w = Window();
if (w)
{
w->DispWindow->SetCaptionTextColor(color.toBgra8());
w->DispWindow->Update();
}
}
void Widget::Update()
{
Widget* w = Window();
if (w)
{
w->DispWindow->Update();
}
}
void Widget::Repaint()
{
Widget* w = Window();
w->DispCanvas->begin(WindowBackground);
w->Paint(DispCanvas.get());
w->DispCanvas->end();
}
void Widget::Paint(Canvas* canvas)
{
Point oldOrigin = canvas->getOrigin();
canvas->pushClip(FrameGeometry);
canvas->setOrigin(oldOrigin + FrameGeometry.topLeft());
OnPaintFrame(canvas);
canvas->setOrigin(oldOrigin);
canvas->popClip();
canvas->pushClip(ContentGeometry);
canvas->setOrigin(oldOrigin + ContentGeometry.topLeft());
OnPaint(canvas);
for (Widget* w = FirstChild(); w != nullptr; w = w->NextSibling())
{
if (w->Type == WidgetType::Child)
w->Paint(canvas);
}
canvas->setOrigin(oldOrigin);
canvas->popClip();
}
bool Widget::GetKeyState(EInputKey key)
{
Widget* window = Window();
return window ? window->DispWindow->GetKeyState(key) : false;
}
bool Widget::HasFocus()
{
Widget* window = Window();
return window ? window->FocusWidget == this : false;
}
bool Widget::IsEnabled()
{
return true;
}
bool Widget::IsVisible()
{
return true;
}
void Widget::SetFocus()
{
Widget* window = Window();
if (window)
{
if (window->FocusWidget)
window->FocusWidget->OnLostFocus();
window->FocusWidget = this;
window->FocusWidget->OnSetFocus();
window->ActivateWindow();
}
}
void Widget::SetEnabled(bool value)
{
}
void Widget::LockCursor()
{
Widget* w = Window();
if (w && w->CaptureWidget != this)
{
w->CaptureWidget = this;
w->DispWindow->LockCursor();
}
}
void Widget::UnlockCursor()
{
Widget* w = Window();
if (w && w->CaptureWidget != nullptr)
{
w->CaptureWidget = nullptr;
w->DispWindow->UnlockCursor();
}
}
void Widget::SetCursor(StandardCursor cursor)
{
}
void Widget::CaptureMouse()
{
}
void Widget::ReleaseMouseCapture()
{
}
std::string Widget::GetClipboardText()
{
return {};
}
void Widget::SetClipboardText(const std::string& text)
{
}
Widget* Widget::Window()
{
for (Widget* w = this; w != nullptr; w = w->Parent())
{
if (w->DispWindow)
return w;
}
return nullptr;
}
Canvas* Widget::GetCanvas()
{
for (Widget* w = this; w != nullptr; w = w->Parent())
{
if (w->DispCanvas)
return w->DispCanvas.get();
}
return nullptr;
}
Widget* Widget::ChildAt(const Point& pos)
{
for (Widget* cur = LastChild(); cur != nullptr; cur = cur->PrevSibling())
{
if (cur->FrameGeometry.contains(pos))
{
Widget* cur2 = cur->ChildAt(pos - cur->FrameGeometry.topLeft());
return cur2 ? cur2 : cur;
}
}
return nullptr;
}
Point Widget::MapFrom(const Widget* parent, const Point& pos) const
{
Point p = pos;
for (const Widget* cur = this; cur != nullptr; cur = cur->Parent())
{
if (cur == parent)
return p;
p -= cur->ContentGeometry.topLeft();
}
throw std::runtime_error("MapFrom: not a parent of widget");
}
Point Widget::MapFromGlobal(const Point& pos) const
{
Point p = pos;
for (const Widget* cur = this; cur != nullptr; cur = cur->Parent())
{
if (cur->DispWindow)
{
return p - cur->GetFrameGeometry().topLeft();
}
p -= cur->ContentGeometry.topLeft();
}
throw std::runtime_error("MapFromGlobal: no window widget found");
}
Point Widget::MapTo(const Widget* parent, const Point& pos) const
{
Point p = pos;
for (const Widget* cur = this; cur != nullptr; cur = cur->Parent())
{
if (cur == parent)
return p;
p += cur->ContentGeometry.topLeft();
}
throw std::runtime_error("MapTo: not a parent of widget");
}
Point Widget::MapToGlobal(const Point& pos) const
{
Point p = pos;
for (const Widget* cur = this; cur != nullptr; cur = cur->Parent())
{
if (cur->DispWindow)
{
return cur->GetFrameGeometry().topLeft() + p;
}
p += cur->ContentGeometry.topLeft();
}
throw std::runtime_error("MapFromGlobal: no window widget found");
}
void Widget::OnWindowPaint()
{
Repaint();
}
void Widget::OnWindowMouseMove(const Point& pos)
{
if (CaptureWidget)
{
CaptureWidget->OnMouseMove(CaptureWidget->MapFrom(this, pos));
}
else
{
Widget* widget = ChildAt(pos);
if (!widget)
widget = this;
widget->OnMouseMove(widget->MapFrom(this, pos));
}
}
void Widget::OnWindowMouseDown(const Point& pos, EInputKey key)
{
if (CaptureWidget)
{
CaptureWidget->OnMouseDown(CaptureWidget->MapFrom(this, pos), key);
}
else
{
Widget* widget = ChildAt(pos);
if (!widget)
widget = this;
widget->OnMouseDown(widget->MapFrom(this, pos), key);
}
}
void Widget::OnWindowMouseDoubleclick(const Point& pos, EInputKey key)
{
if (CaptureWidget)
{
CaptureWidget->OnMouseDoubleclick(CaptureWidget->MapFrom(this, pos), key);
}
else
{
Widget* widget = ChildAt(pos);
if (!widget)
widget = this;
widget->OnMouseDoubleclick(widget->MapFrom(this, pos), key);
}
}
void Widget::OnWindowMouseUp(const Point& pos, EInputKey key)
{
if (CaptureWidget)
{
CaptureWidget->OnMouseUp(CaptureWidget->MapFrom(this, pos), key);
}
else
{
Widget* widget = ChildAt(pos);
if (!widget)
widget = this;
widget->OnMouseUp(widget->MapFrom(this, pos), key);
}
}
void Widget::OnWindowMouseWheel(const Point& pos, EInputKey key)
{
if (CaptureWidget)
{
CaptureWidget->OnMouseWheel(CaptureWidget->MapFrom(this, pos), key);
}
else
{
Widget* widget = ChildAt(pos);
if (!widget)
widget = this;
widget->OnMouseWheel(widget->MapFrom(this, pos), key);
}
}
void Widget::OnWindowRawMouseMove(int dx, int dy)
{
if (CaptureWidget)
{
CaptureWidget->OnRawMouseMove(dx, dy);
}
else if (FocusWidget)
{
FocusWidget->OnRawMouseMove(dx, dy);
}
}
void Widget::OnWindowKeyChar(std::string chars)
{
if (FocusWidget)
FocusWidget->OnKeyChar(chars);
}
void Widget::OnWindowKeyDown(EInputKey key)
{
if (FocusWidget)
FocusWidget->OnKeyDown(key);
}
void Widget::OnWindowKeyUp(EInputKey key)
{
if (FocusWidget)
FocusWidget->OnKeyUp(key);
}
void Widget::OnWindowGeometryChanged()
{
Size size = DispWindow->GetClientSize();
FrameGeometry = Rect::xywh(0.0, 0.0, size.width, size.height);
ContentGeometry = FrameGeometry;
OnGeometryChanged();
}
void Widget::OnWindowClose()
{
Close();
}
void Widget::OnWindowActivated()
{
}
void Widget::OnWindowDeactivated()
{
}
void Widget::OnWindowDpiScaleChanged()
{
}

View file

@ -0,0 +1,47 @@
#include "widgets/checkboxlabel/checkboxlabel.h"
CheckboxLabel::CheckboxLabel(Widget* parent) : Widget(parent)
{
}
void CheckboxLabel::SetText(const std::string& value)
{
if (text != value)
{
text = value;
Update();
}
}
const std::string& CheckboxLabel::GetText() const
{
return text;
}
void CheckboxLabel::SetChecked(bool value)
{
if (value != checked)
{
checked = value;
Update();
}
}
bool CheckboxLabel::GetChecked() const
{
return checked;
}
double CheckboxLabel::GetPreferredHeight() const
{
return 20.0;
}
void CheckboxLabel::OnPaint(Canvas* canvas)
{
if (checked)
canvas->drawText(Point(0.0, GetHeight() - 5.0), Colorf::fromRgba8(255, 255, 255), "[x] " + text);
else
canvas->drawText(Point(0.0, GetHeight() - 5.0), Colorf::fromRgba8(255, 255, 255), "[ ] " + text);
}

View file

@ -0,0 +1,32 @@
#include "widgets/imagebox/imagebox.h"
ImageBox::ImageBox(Widget* parent) : Widget(parent)
{
}
double ImageBox::GetPreferredHeight() const
{
if (image)
return (double)image->GetHeight();
else
return 0.0;
}
void ImageBox::SetImage(std::shared_ptr<Image> newImage)
{
if (image != newImage)
{
image = newImage;
Update();
}
}
void ImageBox::OnPaint(Canvas* canvas)
{
canvas->fillRect(Rect::xywh(0.0, 0.0, GetWidth(), GetHeight()), Colorf::fromRgba8(0, 0, 0));
if (image)
{
canvas->drawImage(image, Point((GetWidth() - (double)image->GetWidth()) * 0.5, (GetHeight() - (double)image->GetHeight()) * 0.5));
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,45 @@
#include "widgets/listview/listview.h"
ListView::ListView(Widget* parent) : Widget(parent)
{
SetNoncontentSizes(10.0, 5.0, 10.0, 5.0);
}
void ListView::AddItem(const std::string& text)
{
items.push_back(text);
Update();
}
void ListView::OnPaint(Canvas* canvas)
{
double y = 20.0;
double x = 2.0;
double w = GetFrameGeometry().width;
double h = 20.0;
int index = 0;
for (const std::string& item : items)
{
if (index == 0)
{
canvas->fillRect(Rect::xywh(x - 2.0, y + 5.0 - h, w, h), Colorf::fromRgba8(100, 100, 100));
}
canvas->drawText(Point(x, y), Colorf::fromRgba8(255, 255, 255), item);
y += h;
index++;
}
}
void ListView::OnPaintFrame(Canvas* canvas)
{
double w = GetFrameGeometry().width;
double h = GetFrameGeometry().height;
Colorf bordercolor = Colorf::fromRgba8(100, 100, 100);
canvas->fillRect(Rect::xywh(0.0, 0.0, w, h), Colorf::fromRgba8(38, 38, 38));
canvas->fillRect(Rect::xywh(0.0, 0.0, w, 1.0), bordercolor);
canvas->fillRect(Rect::xywh(0.0, h - 1.0, w, 1.0), bordercolor);
canvas->fillRect(Rect::xywh(0.0, 0.0, 1.0, h - 0.0), bordercolor);
canvas->fillRect(Rect::xywh(w - 1.0, 0.0, 1.0, h - 0.0), bordercolor);
}

View file

@ -0,0 +1,40 @@
#include "widgets/mainwindow/mainwindow.h"
#include "widgets/menubar/menubar.h"
#include "widgets/toolbar/toolbar.h"
#include "widgets/statusbar/statusbar.h"
MainWindow::MainWindow() : Widget(nullptr, WidgetType::Window)
{
MenubarWidget = new Menubar(this);
// ToolbarWidget = new Toolbar(this);
StatusbarWidget = new Statusbar(this);
}
MainWindow::~MainWindow()
{
}
void MainWindow::SetCentralWidget(Widget* widget)
{
if (CentralWidget != widget)
{
delete CentralWidget;
CentralWidget = widget;
if (CentralWidget)
CentralWidget->SetParent(this);
OnGeometryChanged();
}
}
void MainWindow::OnGeometryChanged()
{
Size s = GetSize();
MenubarWidget->SetFrameGeometry(0.0, 0.0, s.width, 32.0);
// ToolbarWidget->SetFrameGeometry(0.0, 32.0, s.width, 36.0);
StatusbarWidget->SetFrameGeometry(0.0, s.height - 32.0, s.width, 32.0);
if (CentralWidget)
CentralWidget->SetFrameGeometry(0.0, 32.0, s.width, s.height - 32.0 - 32.0);
}

View file

@ -0,0 +1,16 @@
#include "widgets/menubar/menubar.h"
#include "core/colorf.h"
Menubar::Menubar(Widget* parent) : Widget(parent)
{
}
Menubar::~Menubar()
{
}
void Menubar::OnPaint(Canvas* canvas)
{
canvas->drawText(Point(16.0, 21.0), Colorf::fromRgba8(0, 0, 0), "File Edit View Tools Window Help");
}

View file

@ -0,0 +1,44 @@
#include "widgets/pushbutton/pushbutton.h"
PushButton::PushButton(Widget* parent) : Widget(parent)
{
SetNoncontentSizes(10.0, 5.0, 10.0, 5.0);
}
void PushButton::SetText(const std::string& value)
{
if (text != value)
{
text = value;
Update();
}
}
const std::string& PushButton::GetText() const
{
return text;
}
double PushButton::GetPreferredHeight() const
{
return 30.0;
}
void PushButton::OnPaintFrame(Canvas* canvas)
{
double w = GetFrameGeometry().width;
double h = GetFrameGeometry().height;
Colorf bordercolor = Colorf::fromRgba8(100, 100, 100);
canvas->fillRect(Rect::xywh(0.0, 0.0, w, h), Colorf::fromRgba8(68, 68, 68));
canvas->fillRect(Rect::xywh(0.0, 0.0, w, 1.0), bordercolor);
canvas->fillRect(Rect::xywh(0.0, h - 1.0, w, 1.0), bordercolor);
canvas->fillRect(Rect::xywh(0.0, 0.0, 1.0, h - 0.0), bordercolor);
canvas->fillRect(Rect::xywh(w - 1.0, 0.0, 1.0, h - 0.0), bordercolor);
}
void PushButton::OnPaint(Canvas* canvas)
{
Rect box = canvas->measureText(text);
canvas->drawText(Point((GetWidth() - box.width) * 0.5, GetHeight() - 5.0), Colorf::fromRgba8(255, 255, 255), text);
}

View file

@ -0,0 +1,400 @@
#include "widgets/scrollbar/scrollbar.h"
#include "core/colorf.h"
#include <stdexcept>
Scrollbar::Scrollbar(Widget* parent) : Widget(parent)
{
UpdatePartPositions();
mouse_down_timer = new Timer(this);
mouse_down_timer->FuncExpired = [=]() { OnTimerExpired(); };
}
Scrollbar::~Scrollbar()
{
}
bool Scrollbar::IsVertical() const
{
return vertical;
}
bool Scrollbar::IsHorizontal() const
{
return !vertical;
}
int Scrollbar::GetMin() const
{
return scroll_min;
}
int Scrollbar::GetMax() const
{
return scroll_max;
}
int Scrollbar::GetLineStep() const
{
return line_step;
}
int Scrollbar::GetPageStep() const
{
return page_step;
}
int Scrollbar::GetPosition() const
{
return position;
}
void Scrollbar::SetVertical()
{
vertical = true;
if (UpdatePartPositions())
Update();
}
void Scrollbar::SetHorizontal()
{
vertical = false;
if (UpdatePartPositions())
Update();
}
void Scrollbar::SetMin(int new_scroll_min)
{
SetRanges(new_scroll_min, scroll_max, line_step, page_step);
}
void Scrollbar::SetMax(int new_scroll_max)
{
SetRanges(scroll_min, new_scroll_max, line_step, page_step);
}
void Scrollbar::SetLineStep(int step)
{
SetRanges(scroll_min, scroll_max, step, page_step);
}
void Scrollbar::SetPageStep(int step)
{
SetRanges(scroll_min, scroll_max, line_step, step);
}
void Scrollbar::SetRanges(int scroll_min, int scroll_max, int line_step, int page_step)
{
if (scroll_min >= scroll_max || line_step <= 0 || page_step <= 0)
throw std::runtime_error("Scrollbar ranges out of bounds!");
scroll_min = scroll_min;
scroll_max = scroll_max;
line_step = line_step;
page_step = page_step;
if (position >= scroll_max)
position = scroll_max - 1;
if (position < scroll_min)
position = scroll_min;
if (UpdatePartPositions())
Update();
}
void Scrollbar::SetRanges(int view_size, int total_size)
{
if (view_size <= 0 || total_size <= 0)
{
SetRanges(0, 1, 1, 1);
}
else
{
int scroll_max = std::max(1, total_size - view_size + 1);
int page_step = std::max(1, view_size);
SetRanges(0, scroll_max, 1, page_step);
}
}
void Scrollbar::SetPosition(int pos)
{
position = pos;
if (pos >= scroll_max)
position = scroll_max - 1;
if (pos < scroll_min)
position = scroll_min;
if (UpdatePartPositions())
Update();
}
void Scrollbar::OnMouseMove(const Point& pos)
{
if (mouse_down_mode == mouse_down_thumb_drag)
{
int last_position = position;
if (pos.x < -100 || pos.x > GetWidth() + 100 || pos.y < -100 || pos.y > GetHeight() + 100)
{
position = thumb_start_position;
}
else
{
int delta = (int)(vertical ? (pos.y - mouse_drag_start_pos.y) : (pos.x - mouse_drag_start_pos.x));
int position_pixels = thumb_start_pixel_position + delta;
int track_height = 0;
if (vertical)
track_height = (int)(rect_track_decrement.height + rect_track_increment.height);
else
track_height = (int)(rect_track_decrement.width + rect_track_increment.width);
if (track_height != 0)
position = scroll_min + position_pixels * (scroll_max - scroll_min) / track_height;
else
position = 0;
if (position >= scroll_max)
position = scroll_max - 1;
if (position < scroll_min)
position = scroll_min;
}
if (position != last_position)
{
InvokeScrollEvent(&FuncScrollThumbTrack);
UpdatePartPositions();
}
}
Update();
}
void Scrollbar::OnMouseDown(const Point& pos, int key)
{
mouse_drag_start_pos = pos;
if (rect_button_decrement.contains(pos))
{
mouse_down_mode = mouse_down_button_decr;
FuncScrollOnMouseDown = &FuncScrollLineDecrement;
int last_position = position;
position -= line_step;
last_step_size = -line_step;
if (position >= scroll_max)
position = scroll_max - 1;
if (position < scroll_min)
position = scroll_min;
if (last_position != position)
InvokeScrollEvent(&FuncScrollLineDecrement);
}
else if (rect_button_increment.contains(pos))
{
mouse_down_mode = mouse_down_button_incr;
FuncScrollOnMouseDown = &FuncScrollLineIncrement;
int last_position = position;
position += line_step;
last_step_size = line_step;
if (position >= scroll_max)
position = scroll_max - 1;
if (position < scroll_min)
position = scroll_min;
if (last_position != position)
InvokeScrollEvent(&FuncScrollLineIncrement);
}
else if (rect_thumb.contains(pos))
{
mouse_down_mode = mouse_down_thumb_drag;
thumb_start_position = position;
thumb_start_pixel_position = (int)(vertical ? (rect_thumb.y - rect_track_decrement.y) : (rect_thumb.x - rect_track_decrement.x));
}
else if (rect_track_decrement.contains(pos))
{
mouse_down_mode = mouse_down_track_decr;
FuncScrollOnMouseDown = &FuncScrollPageDecrement;
int last_position = position;
position -= page_step;
last_step_size = -page_step;
if (position >= scroll_max)
position = scroll_max - 1;
if (position < scroll_min)
position = scroll_min;
if (last_position != position)
InvokeScrollEvent(&FuncScrollPageDecrement);
}
else if (rect_track_increment.contains(pos))
{
mouse_down_mode = mouse_down_track_incr;
FuncScrollOnMouseDown = &FuncScrollPageIncrement;
int last_position = position;
position += page_step;
last_step_size = page_step;
if (position >= scroll_max)
position = scroll_max - 1;
if (position < scroll_min)
position = scroll_min;
if (last_position != position)
InvokeScrollEvent(&FuncScrollPageIncrement);
}
mouse_down_timer->Start(100, false);
UpdatePartPositions();
Update();
CaptureMouse();
}
void Scrollbar::OnMouseUp(const Point& pos, int key)
{
if (mouse_down_mode == mouse_down_thumb_drag)
{
if (FuncScrollThumbRelease)
FuncScrollThumbRelease();
}
mouse_down_mode = mouse_down_none;
mouse_down_timer->Stop();
Update();
ReleaseMouseCapture();
}
void Scrollbar::OnMouseLeave()
{
Update();
}
void Scrollbar::OnGeometryChanged()
{
UpdatePartPositions();
}
void Scrollbar::OnPaint(Canvas* canvas)
{
/*
part_button_decrement.render_box(canvas, rect_button_decrement);
part_track_decrement.render_box(canvas, rect_track_decrement);
part_thumb.render_box(canvas, rect_thumb);
part_thumb_gripper.render_box(canvas, rect_thumb);
part_track_increment.render_box(canvas, rect_track_increment);
part_button_increment.render_box(canvas, rect_button_increment);
*/
}
// Calculates positions of all parts. Returns true if thumb position was changed compared to previously, false otherwise.
bool Scrollbar::UpdatePartPositions()
{
int total_height = (int)(vertical ? GetHeight() : GetWidth());
int track_height = std::max(0, total_height - decr_height - incr_height);
int thumb_height = CalculateThumbSize(track_height);
int thumb_offset = decr_height + CalculateThumbPosition(thumb_height, track_height);
Rect previous_rect_thumb = rect_thumb;
rect_button_decrement = CreateRect(0, decr_height);
rect_track_decrement = CreateRect(decr_height, thumb_offset);
rect_thumb = CreateRect(thumb_offset, thumb_offset + thumb_height);
rect_track_increment = CreateRect(thumb_offset + thumb_height, decr_height + track_height);
rect_button_increment = CreateRect(decr_height + track_height, decr_height + track_height + incr_height);
return (previous_rect_thumb != rect_thumb);
}
int Scrollbar::CalculateThumbSize(int track_size)
{
int minimum_thumb_size = 20;
int range = scroll_max - scroll_min;
int length = range + page_step - 1;
int thumb_size = page_step * track_size / length;
if (thumb_size < minimum_thumb_size)
thumb_size = minimum_thumb_size;
if (thumb_size > track_size)
thumb_size = track_size;
return thumb_size;
}
int Scrollbar::CalculateThumbPosition(int thumb_size, int track_size)
{
int relative_pos = position - scroll_min;
int range = scroll_max - scroll_min - 1;
if (range != 0)
{
int available_area = std::max(0, track_size - thumb_size);
return relative_pos * available_area / range;
}
else
{
return 0;
}
}
Rect Scrollbar::CreateRect(int start, int end)
{
if (vertical)
return Rect(0.0, start, GetWidth(), end - start);
else
return Rect(start, 0.0, end - start, GetHeight());
}
void Scrollbar::OnTimerExpired()
{
if (mouse_down_mode == mouse_down_thumb_drag)
return;
mouse_down_timer->Start(100, false);
int last_position = position;
position += last_step_size;
if (position >= scroll_max)
position = scroll_max - 1;
if (position < scroll_min)
position = scroll_min;
if (position != last_position)
{
InvokeScrollEvent(FuncScrollOnMouseDown);
if (UpdatePartPositions())
Update();
}
}
void Scrollbar::OnEnableChanged()
{
Update();
}
void Scrollbar::InvokeScrollEvent(std::function<void()>* event_ptr)
{
if (position == scroll_max - 1)
{
if (FuncScrollMax)
FuncScrollMax();
}
if (position == scroll_min)
{
if (FuncScrollMin)
FuncScrollMin();
}
if (FuncScroll)
FuncScroll();
if (event_ptr)
(*event_ptr)();
}

View file

@ -0,0 +1,19 @@
#include "widgets/statusbar/statusbar.h"
#include "widgets/lineedit/lineedit.h"
#include "core/colorf.h"
Statusbar::Statusbar(Widget* parent) : Widget(parent)
{
CommandEdit = new LineEdit(this);
CommandEdit->SetFrameGeometry(Rect::xywh(90.0, 4.0, 400.0, 23.0));
}
Statusbar::~Statusbar()
{
}
void Statusbar::OnPaint(Canvas* canvas)
{
canvas->drawText(Point(16.0, 21.0), Colorf::fromRgba8(0, 0, 0), "Command:");
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,30 @@
#include "widgets/textlabel/textlabel.h"
TextLabel::TextLabel(Widget* parent) : Widget(parent)
{
}
void TextLabel::SetText(const std::string& value)
{
if (text != value)
{
text = value;
Update();
}
}
const std::string& TextLabel::GetText() const
{
return text;
}
double TextLabel::GetPreferredHeight() const
{
return 20.0;
}
void TextLabel::OnPaint(Canvas* canvas)
{
canvas->drawText(Point(0.0, GetHeight() - 5.0), Colorf::fromRgba8(255, 255, 255), text);
}

View file

@ -0,0 +1,10 @@
#include "widgets/toolbar/toolbar.h"
Toolbar::Toolbar(Widget* parent) : Widget(parent)
{
}
Toolbar::~Toolbar()
{
}

View file

@ -0,0 +1,14 @@
#include "widgets/toolbar/toolbarbutton.h"
ToolbarButton::ToolbarButton(Widget* parent) : Widget(parent)
{
}
ToolbarButton::~ToolbarButton()
{
}
void ToolbarButton::OnPaint(Canvas* canvas)
{
}

View file

@ -0,0 +1,485 @@
#include "win32window.h"
#include <windowsx.h>
#include <stdexcept>
#include <cmath>
#include <vector>
#include <dwmapi.h>
#pragma comment(lib, "dwmapi.lib")
#ifndef HID_USAGE_PAGE_GENERIC
#define HID_USAGE_PAGE_GENERIC ((USHORT) 0x01)
#endif
#ifndef HID_USAGE_GENERIC_MOUSE
#define HID_USAGE_GENERIC_MOUSE ((USHORT) 0x02)
#endif
#ifndef HID_USAGE_GENERIC_JOYSTICK
#define HID_USAGE_GENERIC_JOYSTICK ((USHORT) 0x04)
#endif
#ifndef HID_USAGE_GENERIC_GAMEPAD
#define HID_USAGE_GENERIC_GAMEPAD ((USHORT) 0x05)
#endif
#ifndef RIDEV_INPUTSINK
#define RIDEV_INPUTSINK (0x100)
#endif
static std::string from_utf16(const std::wstring& str)
{
if (str.empty()) return {};
int needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0, nullptr, nullptr);
if (needed == 0)
throw std::runtime_error("WideCharToMultiByte failed");
std::string result;
result.resize(needed);
needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size(), nullptr, nullptr);
if (needed == 0)
throw std::runtime_error("WideCharToMultiByte failed");
return result;
}
static std::wstring to_utf16(const std::string& str)
{
if (str.empty()) return {};
int needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0);
if (needed == 0)
throw std::runtime_error("MultiByteToWideChar failed");
std::wstring result;
result.resize(needed);
needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size());
if (needed == 0)
throw std::runtime_error("MultiByteToWideChar failed");
return result;
}
Win32Window::Win32Window(DisplayWindowHost* windowHost) : WindowHost(windowHost)
{
Windows.push_front(this);
WindowsIterator = Windows.begin();
WNDCLASSEX classdesc = {};
classdesc.cbSize = sizeof(WNDCLASSEX);
classdesc.hInstance = GetModuleHandle(0);
classdesc.style = CS_VREDRAW | CS_HREDRAW;
classdesc.lpszClassName = L"ZWidgetWindow";
classdesc.lpfnWndProc = &Win32Window::WndProc;
RegisterClassEx(&classdesc);
CreateWindowEx(WS_EX_APPWINDOW, L"ZWidgetWindow", L"", WS_OVERLAPPEDWINDOW, 0, 0, 100, 100, 0, 0, GetModuleHandle(0), this);
/*
RAWINPUTDEVICE rid;
rid.usUsagePage = HID_USAGE_PAGE_GENERIC;
rid.usUsage = HID_USAGE_GENERIC_MOUSE;
rid.dwFlags = RIDEV_INPUTSINK;
rid.hwndTarget = WindowHandle;
BOOL result = RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE));
*/
}
Win32Window::~Win32Window()
{
if (WindowHandle)
{
DestroyWindow(WindowHandle);
WindowHandle = 0;
}
Windows.erase(WindowsIterator);
}
void Win32Window::SetWindowTitle(const std::string& text)
{
SetWindowText(WindowHandle, to_utf16(text).c_str());
}
void Win32Window::SetBorderColor(uint32_t bgra8)
{
bgra8 = bgra8 & 0x00ffffff;
DwmSetWindowAttribute(WindowHandle, 34/*DWMWA_BORDER_COLOR*/, &bgra8, sizeof(uint32_t));
}
void Win32Window::SetCaptionColor(uint32_t bgra8)
{
bgra8 = bgra8 & 0x00ffffff;
DwmSetWindowAttribute(WindowHandle, 35/*DWMWA_CAPTION_COLOR*/, &bgra8, sizeof(uint32_t));
}
void Win32Window::SetCaptionTextColor(uint32_t bgra8)
{
bgra8 = bgra8 & 0x00ffffff;
DwmSetWindowAttribute(WindowHandle, 36/*DWMWA_TEXT_COLOR*/, &bgra8, sizeof(uint32_t));
}
void Win32Window::SetWindowFrame(const Rect& box)
{
double dpiscale = GetDpiScale();
SetWindowPos(WindowHandle, nullptr, (int)std::round(box.x * dpiscale), (int)std::round(box.y * dpiscale), (int)std::round(box.width * dpiscale), (int)std::round(box.height * dpiscale), SWP_NOACTIVATE | SWP_NOZORDER);
}
void Win32Window::SetClientFrame(const Rect& box)
{
double dpiscale = GetDpiScale();
RECT rect = {};
rect.left = (int)std::round(box.x * dpiscale);
rect.top = (int)std::round(box.y * dpiscale);
rect.right = rect.left + (int)std::round(box.width * dpiscale);
rect.bottom = rect.top + (int)std::round(box.height * dpiscale);
DWORD style = (DWORD)GetWindowLongPtr(WindowHandle, GWL_STYLE);
DWORD exstyle = (DWORD)GetWindowLongPtr(WindowHandle, GWL_EXSTYLE);
AdjustWindowRectExForDpi(&rect, style, FALSE, exstyle, GetDpiForWindow(WindowHandle));
SetWindowPos(WindowHandle, nullptr, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOZORDER);
}
void Win32Window::Show()
{
ShowWindow(WindowHandle, SW_SHOW);
}
void Win32Window::ShowFullscreen()
{
HDC screenDC = GetDC(0);
int width = GetDeviceCaps(screenDC, HORZRES);
int height = GetDeviceCaps(screenDC, VERTRES);
ReleaseDC(0, screenDC);
SetWindowLongPtr(WindowHandle, GWL_EXSTYLE, WS_EX_APPWINDOW);
SetWindowLongPtr(WindowHandle, GWL_STYLE, WS_OVERLAPPED);
SetWindowPos(WindowHandle, HWND_TOP, 0, 0, width, height, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
Fullscreen = true;
}
void Win32Window::ShowMaximized()
{
ShowWindow(WindowHandle, SW_SHOWMAXIMIZED);
}
void Win32Window::ShowMinimized()
{
ShowWindow(WindowHandle, SW_SHOWMINIMIZED);
}
void Win32Window::ShowNormal()
{
ShowWindow(WindowHandle, SW_NORMAL);
}
void Win32Window::Hide()
{
ShowWindow(WindowHandle, SW_HIDE);
}
void Win32Window::Activate()
{
SetFocus(WindowHandle);
}
void Win32Window::ShowCursor(bool enable)
{
}
void Win32Window::LockCursor()
{
if (!MouseLocked)
{
MouseLocked = true;
GetCursorPos(&MouseLockPos);
::ShowCursor(FALSE);
}
}
void Win32Window::UnlockCursor()
{
if (MouseLocked)
{
MouseLocked = false;
SetCursorPos(MouseLockPos.x, MouseLockPos.y);
::ShowCursor(TRUE);
}
}
void Win32Window::Update()
{
InvalidateRect(WindowHandle, nullptr, FALSE);
}
bool Win32Window::GetKeyState(EInputKey key)
{
return ::GetKeyState((int)key) & 0x8000; // High bit (0x8000) means key is down, Low bit (0x0001) means key is sticky on (like Caps Lock, Num Lock, etc.)
}
Rect Win32Window::GetWindowFrame() const
{
RECT box = {};
GetWindowRect(WindowHandle, &box);
double dpiscale = GetDpiScale();
return Rect(box.left / dpiscale, box.top / dpiscale, box.right / dpiscale, box.bottom / dpiscale);
}
Size Win32Window::GetClientSize() const
{
RECT box = {};
GetClientRect(WindowHandle, &box);
double dpiscale = GetDpiScale();
return Size(box.right / dpiscale, box.bottom / dpiscale);
}
int Win32Window::GetPixelWidth() const
{
RECT box = {};
GetClientRect(WindowHandle, &box);
return box.right;
}
int Win32Window::GetPixelHeight() const
{
RECT box = {};
GetClientRect(WindowHandle, &box);
return box.bottom;
}
double Win32Window::GetDpiScale() const
{
return GetDpiForWindow(WindowHandle) / 96.0;
}
void Win32Window::PresentBitmap(int width, int height, const uint32_t* pixels)
{
BITMAPV5HEADER header = {};
header.bV5Size = sizeof(BITMAPV5HEADER);
header.bV5Width = width;
header.bV5Height = -height;
header.bV5Planes = 1;
header.bV5BitCount = 32;
header.bV5Compression = BI_BITFIELDS;
header.bV5AlphaMask = 0xff000000;
header.bV5RedMask = 0x00ff0000;
header.bV5GreenMask = 0x0000ff00;
header.bV5BlueMask = 0x000000ff;
header.bV5SizeImage = width * height * sizeof(uint32_t);
header.bV5CSType = LCS_sRGB;
HDC dc = PaintDC;
if (dc != 0)
{
int result = SetDIBitsToDevice(dc, 0, 0, width, height, 0, 0, 0, height, pixels, (const BITMAPINFO*)&header, BI_RGB);
ReleaseDC(WindowHandle, dc);
}
}
LRESULT Win32Window::OnWindowMessage(UINT msg, WPARAM wparam, LPARAM lparam)
{
LPARAM result = 0;
if (DwmDefWindowProc(WindowHandle, msg, wparam, lparam, &result))
return result;
if (msg == WM_INPUT)
{
bool hasFocus = GetFocus() != 0;
HRAWINPUT handle = (HRAWINPUT)lparam;
UINT size = 0;
UINT result = GetRawInputData(handle, RID_INPUT, 0, &size, sizeof(RAWINPUTHEADER));
if (result == 0 && size > 0)
{
size *= 2;
std::vector<uint8_t*> buffer(size);
result = GetRawInputData(handle, RID_INPUT, buffer.data(), &size, sizeof(RAWINPUTHEADER));
if (result >= 0)
{
RAWINPUT* rawinput = (RAWINPUT*)buffer.data();
if (rawinput->header.dwType == RIM_TYPEMOUSE)
{
if (hasFocus)
WindowHost->OnWindowRawMouseMove(rawinput->data.mouse.lLastX, rawinput->data.mouse.lLastY);
}
}
}
return DefWindowProc(WindowHandle, msg, wparam, lparam);
}
else if (msg == WM_PAINT)
{
PAINTSTRUCT paintStruct = {};
PaintDC = BeginPaint(WindowHandle, &paintStruct);
if (PaintDC)
{
WindowHost->OnWindowPaint();
EndPaint(WindowHandle, &paintStruct);
PaintDC = 0;
}
return 0;
}
else if (msg == WM_ACTIVATE)
{
WindowHost->OnWindowActivated();
}
else if (msg == WM_MOUSEMOVE)
{
if (MouseLocked && GetFocus() != 0)
{
RECT box = {};
GetClientRect(WindowHandle, &box);
POINT center = {};
center.x = box.right / 2;
center.y = box.bottom / 2;
ClientToScreen(WindowHandle, &center);
SetCursorPos(center.x, center.y);
}
else
{
SetCursor((HCURSOR)LoadImage(0, IDC_ARROW, IMAGE_CURSOR, LR_DEFAULTSIZE, LR_DEFAULTSIZE, LR_SHARED));
}
WindowHost->OnWindowMouseMove(GetLParamPos(lparam));
}
else if (msg == WM_LBUTTONDOWN)
{
WindowHost->OnWindowMouseDown(GetLParamPos(lparam), IK_LeftMouse);
}
else if (msg == WM_LBUTTONUP)
{
WindowHost->OnWindowMouseUp(GetLParamPos(lparam), IK_LeftMouse);
}
else if (msg == WM_MBUTTONDOWN)
{
WindowHost->OnWindowMouseDown(GetLParamPos(lparam), IK_MiddleMouse);
}
else if (msg == WM_MBUTTONUP)
{
WindowHost->OnWindowMouseUp(GetLParamPos(lparam), IK_MiddleMouse);
}
else if (msg == WM_RBUTTONDOWN)
{
WindowHost->OnWindowMouseDown(GetLParamPos(lparam), IK_RightMouse);
}
else if (msg == WM_RBUTTONUP)
{
WindowHost->OnWindowMouseUp(GetLParamPos(lparam), IK_RightMouse);
}
else if (msg == WM_MOUSEWHEEL)
{
double delta = GET_WHEEL_DELTA_WPARAM(wparam) / (double)WHEEL_DELTA;
WindowHost->OnWindowMouseWheel(GetLParamPos(lparam), delta < 0.0 ? IK_MouseWheelDown : IK_MouseWheelUp);
}
else if (msg == WM_CHAR)
{
wchar_t buf[2] = { (wchar_t)wparam, 0 };
WindowHost->OnWindowKeyChar(from_utf16(buf));
}
else if (msg == WM_KEYDOWN)
{
WindowHost->OnWindowKeyDown((EInputKey)wparam);
}
else if (msg == WM_KEYUP)
{
WindowHost->OnWindowKeyUp((EInputKey)wparam);
}
else if (msg == WM_SETFOCUS)
{
if (MouseLocked)
{
::ShowCursor(FALSE);
}
}
else if (msg == WM_KILLFOCUS)
{
if (MouseLocked)
{
::ShowCursor(TRUE);
}
}
else if (msg == WM_CLOSE)
{
WindowHost->OnWindowClose();
return 0;
}
else if (msg == WM_SIZE)
{
WindowHost->OnWindowGeometryChanged();
return 0;
}
/*else if (msg == WM_NCCALCSIZE && wparam == TRUE) // calculate client area for the window
{
NCCALCSIZE_PARAMS* calcsize = (NCCALCSIZE_PARAMS*)lparam;
return WVR_REDRAW;
}*/
return DefWindowProc(WindowHandle, msg, wparam, lparam);
}
Point Win32Window::GetLParamPos(LPARAM lparam) const
{
double dpiscale = GetDpiScale();
return Point(GET_X_LPARAM(lparam) / dpiscale, GET_Y_LPARAM(lparam) / dpiscale);
}
LRESULT Win32Window::WndProc(HWND windowhandle, UINT msg, WPARAM wparam, LPARAM lparam)
{
if (msg == WM_CREATE)
{
CREATESTRUCT* createstruct = (CREATESTRUCT*)lparam;
Win32Window* viewport = (Win32Window*)createstruct->lpCreateParams;
viewport->WindowHandle = windowhandle;
SetWindowLongPtr(windowhandle, GWLP_USERDATA, (LONG_PTR)viewport);
return viewport->OnWindowMessage(msg, wparam, lparam);
}
else
{
Win32Window* viewport = (Win32Window*)GetWindowLongPtr(windowhandle, GWLP_USERDATA);
if (viewport)
{
LRESULT result = viewport->OnWindowMessage(msg, wparam, lparam);
if (msg == WM_DESTROY)
{
SetWindowLongPtr(windowhandle, GWLP_USERDATA, 0);
viewport->WindowHandle = 0;
}
return result;
}
else
{
return DefWindowProc(windowhandle, msg, wparam, lparam);
}
}
}
void Win32Window::ProcessEvents()
{
while (true)
{
MSG msg = {};
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE) <= 0)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
void Win32Window::RunLoop()
{
while (!ExitRunLoop && !Windows.empty())
{
MSG msg = {};
if (GetMessage(&msg, 0, 0, 0) <= 0)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
ExitRunLoop = false;
}
void Win32Window::ExitLoop()
{
ExitRunLoop = true;
}
std::list<Win32Window*> Win32Window::Windows;
bool Win32Window::ExitRunLoop;

View file

@ -0,0 +1,69 @@
#pragma once
#define NOMINMAX
#define WIN32_MEAN_AND_LEAN
#ifndef WINVER
#define WINVER 0x0605
#endif
#include <Windows.h>
#include <list>
#include <zwidget/window/window.h>
class Win32Window : public DisplayWindow
{
public:
Win32Window(DisplayWindowHost* windowHost);
~Win32Window();
void SetWindowTitle(const std::string& text) override;
void SetWindowFrame(const Rect& box) override;
void SetClientFrame(const Rect& box) override;
void Show() override;
void ShowFullscreen() override;
void ShowMaximized() override;
void ShowMinimized() override;
void ShowNormal() override;
void Hide() override;
void Activate() override;
void ShowCursor(bool enable) override;
void LockCursor() override;
void UnlockCursor() override;
void Update() override;
bool GetKeyState(EInputKey key) override;
Rect GetWindowFrame() const override;
Size GetClientSize() const override;
int GetPixelWidth() const override;
int GetPixelHeight() const override;
double GetDpiScale() const override;
void PresentBitmap(int width, int height, const uint32_t* pixels) override;
void SetBorderColor(uint32_t bgra8) override;
void SetCaptionColor(uint32_t bgra8) override;
void SetCaptionTextColor(uint32_t bgra8) override;
Point GetLParamPos(LPARAM lparam) const;
static void ProcessEvents();
static void RunLoop();
static void ExitLoop();
static bool ExitRunLoop;
static std::list<Win32Window*> Windows;
std::list<Win32Window*>::iterator WindowsIterator;
LRESULT OnWindowMessage(UINT msg, WPARAM wparam, LPARAM lparam);
static LRESULT CALLBACK WndProc(HWND windowhandle, UINT msg, WPARAM wparam, LPARAM lparam);
DisplayWindowHost* WindowHost = nullptr;
HWND WindowHandle = 0;
bool Fullscreen = false;
bool MouseLocked = false;
POINT MouseLockPos = {};
HDC PaintDC = 0;
};

View file

@ -0,0 +1,50 @@
#include "window/window.h"
#ifdef WIN32
#include "win32/win32window.h"
std::unique_ptr<DisplayWindow> DisplayWindow::Create(DisplayWindowHost* windowHost)
{
return std::make_unique<Win32Window>(windowHost);
}
void DisplayWindow::ProcessEvents()
{
Win32Window::ProcessEvents();
}
void DisplayWindow::RunLoop()
{
Win32Window::RunLoop();
}
void DisplayWindow::ExitLoop()
{
Win32Window::ExitLoop();
}
#else
std::unique_ptr<DisplayWindow> DisplayWindow::Create(DisplayWindowHost* windowHost)
{
throw std::runtime_error("DisplayWindow::Create not implemented");
}
void DisplayWindow::ProcessEvents()
{
throw std::runtime_error("DisplayWindow::ProcessEvents not implemented");
}
void DisplayWindow::RunLoop()
{
throw std::runtime_error("DisplayWindow::RunLoop not implemented");
}
void DisplayWindow::ExitLoop()
{
throw std::runtime_error("DisplayWindow::ExitLoop not implemented");
}
#endif