Add zwidget
This commit is contained in:
parent
b30f1abe25
commit
b2d2f61be0
54 changed files with 9321 additions and 0 deletions
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…
Add table
Add a link
Reference in a new issue