mirror of
https://github.com/ZDoom/gzdoom.git
synced 2025-01-19 08:01:50 +00:00
Add zwidget
This commit is contained in:
parent
e7285cd6d9
commit
113fdc5fcc
54 changed files with 9321 additions and 0 deletions
|
@ -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 )
|
||||
|
|
127
libraries/ZWidget/CMakeLists.txt
Normal file
127
libraries/ZWidget/CMakeLists.txt
Normal 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()
|
21
libraries/ZWidget/LICENSE.md
Normal file
21
libraries/ZWidget/LICENSE.md
Normal 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.
|
2
libraries/ZWidget/README.md
Normal file
2
libraries/ZWidget/README.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
# ZWidget
|
||||
A framework for building user interface applications
|
63
libraries/ZWidget/include/zwidget/core/canvas.h
Normal file
63
libraries/ZWidget/include/zwidget/core/canvas.h
Normal 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;
|
||||
};
|
36
libraries/ZWidget/include/zwidget/core/colorf.h
Normal file
36
libraries/ZWidget/include/zwidget/core/colorf.h
Normal 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;
|
||||
};
|
15
libraries/ZWidget/include/zwidget/core/font.h
Normal file
15
libraries/ZWidget/include/zwidget/core/font.h
Normal 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);
|
||||
};
|
22
libraries/ZWidget/include/zwidget/core/image.h
Normal file
22
libraries/ZWidget/include/zwidget/core/image.h
Normal 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);
|
||||
};
|
74
libraries/ZWidget/include/zwidget/core/rect.h
Normal file
74
libraries/ZWidget/include/zwidget/core/rect.h
Normal 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); }
|
7
libraries/ZWidget/include/zwidget/core/resourcedata.h
Normal file
7
libraries/ZWidget/include/zwidget/core/resourcedata.h
Normal file
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
std::vector<uint8_t> LoadWidgetFontData(const std::string& name);
|
238
libraries/ZWidget/include/zwidget/core/span_layout.h
Normal file
238
libraries/ZWidget/include/zwidget/core/span_layout.h
Normal 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); }
|
||||
};
|
24
libraries/ZWidget/include/zwidget/core/timer.h
Normal file
24
libraries/ZWidget/include/zwidget/core/timer.h
Normal 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;
|
||||
};
|
78
libraries/ZWidget/include/zwidget/core/utf8reader.h
Normal file
78
libraries/ZWidget/include/zwidget/core/utf8reader.h
Normal 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;
|
||||
};
|
181
libraries/ZWidget/include/zwidget/core/widget.h
Normal file
181
libraries/ZWidget/include/zwidget/core/widget.h
Normal 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;
|
||||
};
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
};
|
160
libraries/ZWidget/include/zwidget/widgets/lineedit/lineedit.h
Normal file
160
libraries/ZWidget/include/zwidget/widgets/lineedit/lineedit.h
Normal 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;
|
||||
};
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
};
|
14
libraries/ZWidget/include/zwidget/widgets/menubar/menubar.h
Normal file
14
libraries/ZWidget/include/zwidget/widgets/menubar/menubar.h
Normal 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;
|
||||
};
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
};
|
156
libraries/ZWidget/include/zwidget/widgets/textedit/textedit.h
Normal file
156
libraries/ZWidget/include/zwidget/widgets/textedit/textedit.h
Normal 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); }
|
||||
};
|
|
@ -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;
|
||||
};
|
11
libraries/ZWidget/include/zwidget/widgets/toolbar/toolbar.h
Normal file
11
libraries/ZWidget/include/zwidget/widgets/toolbar/toolbar.h
Normal file
|
@ -0,0 +1,11 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "../../core/widget.h"
|
||||
|
||||
class Toolbar : public Widget
|
||||
{
|
||||
public:
|
||||
Toolbar(Widget* parent);
|
||||
~Toolbar();
|
||||
};
|
|
@ -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;
|
||||
};
|
173
libraries/ZWidget/include/zwidget/window/window.h
Normal file
173
libraries/ZWidget/include/zwidget/window/window.h
Normal 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;
|
||||
};
|
696
libraries/ZWidget/src/core/canvas.cpp
Normal file
696
libraries/ZWidget/src/core/canvas.cpp
Normal 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);
|
||||
}
|
28
libraries/ZWidget/src/core/font.cpp
Normal file
28
libraries/ZWidget/src/core/font.cpp
Normal 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);
|
||||
}
|
43
libraries/ZWidget/src/core/image.cpp
Normal file
43
libraries/ZWidget/src/core/image.cpp
Normal 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);
|
||||
}
|
16
libraries/ZWidget/src/core/schrift/LICENSE.txt
Normal file
16
libraries/ZWidget/src/core/schrift/LICENSE.txt
Normal 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.
|
||||
|
1576
libraries/ZWidget/src/core/schrift/schrift.cpp
Normal file
1576
libraries/ZWidget/src/core/schrift/schrift.cpp
Normal file
File diff suppressed because it is too large
Load diff
95
libraries/ZWidget/src/core/schrift/schrift.h
Normal file
95
libraries/ZWidget/src/core/schrift/schrift.h
Normal 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
|
||||
|
883
libraries/ZWidget/src/core/span_layout.cpp
Normal file
883
libraries/ZWidget/src/core/span_layout.cpp
Normal 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;
|
||||
}
|
||||
}
|
29
libraries/ZWidget/src/core/timer.cpp
Normal file
29
libraries/ZWidget/src/core/timer.cpp
Normal 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()
|
||||
{
|
||||
}
|
153
libraries/ZWidget/src/core/utf8reader.cpp
Normal file
153
libraries/ZWidget/src/core/utf8reader.cpp
Normal 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;
|
||||
}
|
594
libraries/ZWidget/src/core/widget.cpp
Normal file
594
libraries/ZWidget/src/core/widget.cpp
Normal 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()
|
||||
{
|
||||
}
|
|
@ -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);
|
||||
}
|
32
libraries/ZWidget/src/widgets/imagebox/imagebox.cpp
Normal file
32
libraries/ZWidget/src/widgets/imagebox/imagebox.cpp
Normal 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));
|
||||
}
|
||||
}
|
1188
libraries/ZWidget/src/widgets/lineedit/lineedit.cpp
Normal file
1188
libraries/ZWidget/src/widgets/lineedit/lineedit.cpp
Normal file
File diff suppressed because it is too large
Load diff
45
libraries/ZWidget/src/widgets/listview/listview.cpp
Normal file
45
libraries/ZWidget/src/widgets/listview/listview.cpp
Normal 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);
|
||||
}
|
40
libraries/ZWidget/src/widgets/mainwindow/mainwindow.cpp
Normal file
40
libraries/ZWidget/src/widgets/mainwindow/mainwindow.cpp
Normal 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);
|
||||
}
|
16
libraries/ZWidget/src/widgets/menubar/menubar.cpp
Normal file
16
libraries/ZWidget/src/widgets/menubar/menubar.cpp
Normal 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");
|
||||
}
|
44
libraries/ZWidget/src/widgets/pushbutton/pushbutton.cpp
Normal file
44
libraries/ZWidget/src/widgets/pushbutton/pushbutton.cpp
Normal 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);
|
||||
}
|
400
libraries/ZWidget/src/widgets/scrollbar/scrollbar.cpp
Normal file
400
libraries/ZWidget/src/widgets/scrollbar/scrollbar.cpp
Normal 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)();
|
||||
}
|
19
libraries/ZWidget/src/widgets/statusbar/statusbar.cpp
Normal file
19
libraries/ZWidget/src/widgets/statusbar/statusbar.cpp
Normal 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:");
|
||||
}
|
1046
libraries/ZWidget/src/widgets/textedit/textedit.cpp
Normal file
1046
libraries/ZWidget/src/widgets/textedit/textedit.cpp
Normal file
File diff suppressed because it is too large
Load diff
30
libraries/ZWidget/src/widgets/textlabel/textlabel.cpp
Normal file
30
libraries/ZWidget/src/widgets/textlabel/textlabel.cpp
Normal 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);
|
||||
}
|
10
libraries/ZWidget/src/widgets/toolbar/toolbar.cpp
Normal file
10
libraries/ZWidget/src/widgets/toolbar/toolbar.cpp
Normal file
|
@ -0,0 +1,10 @@
|
|||
|
||||
#include "widgets/toolbar/toolbar.h"
|
||||
|
||||
Toolbar::Toolbar(Widget* parent) : Widget(parent)
|
||||
{
|
||||
}
|
||||
|
||||
Toolbar::~Toolbar()
|
||||
{
|
||||
}
|
14
libraries/ZWidget/src/widgets/toolbar/toolbarbutton.cpp
Normal file
14
libraries/ZWidget/src/widgets/toolbar/toolbarbutton.cpp
Normal file
|
@ -0,0 +1,14 @@
|
|||
|
||||
#include "widgets/toolbar/toolbarbutton.h"
|
||||
|
||||
ToolbarButton::ToolbarButton(Widget* parent) : Widget(parent)
|
||||
{
|
||||
}
|
||||
|
||||
ToolbarButton::~ToolbarButton()
|
||||
{
|
||||
}
|
||||
|
||||
void ToolbarButton::OnPaint(Canvas* canvas)
|
||||
{
|
||||
}
|
485
libraries/ZWidget/src/window/win32/win32window.cpp
Normal file
485
libraries/ZWidget/src/window/win32/win32window.cpp
Normal 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, ¢er);
|
||||
|
||||
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;
|
69
libraries/ZWidget/src/window/win32/win32window.h
Normal file
69
libraries/ZWidget/src/window/win32/win32window.h
Normal 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;
|
||||
};
|
50
libraries/ZWidget/src/window/window.cpp
Normal file
50
libraries/ZWidget/src/window/window.cpp
Normal 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
|
Loading…
Reference in a new issue