Add zwidget

This commit is contained in:
Magnus Norddahl 2023-12-27 00:44:40 +01:00
commit b2d2f61be0
54 changed files with 9321 additions and 0 deletions

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

@ -0,0 +1,153 @@
/*
** Copyright (c) 1997-2015 Mark Page
**
** This software is provided 'as-is', without any express or implied
** warranty. In no event will the authors be held liable for any damages
** arising from the use of this software.
**
** Permission is granted to anyone to use this software for any purpose,
** including commercial applications, and to alter it and redistribute it
** freely, subject to the following restrictions:
**
** 1. The origin of this software must not be misrepresented; you must not
** claim that you wrote the original software. If you use this software
** in a product, an acknowledgment in the product documentation would be
** appreciated but is not required.
** 2. Altered source versions must be plainly marked as such, and must not be
** misrepresented as being the original software.
** 3. This notice may not be removed or altered from any source distribution.
**
*/
#include "core/utf8reader.h"
namespace
{
static const char trailing_bytes_for_utf8[256] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5
};
static const unsigned char bitmask_leadbyte_for_utf8[6] =
{
0x7f,
0x1f,
0x0f,
0x07,
0x03,
0x01
};
}
UTF8Reader::UTF8Reader(const std::string::value_type *text, std::string::size_type length) : length(length), data((unsigned char *)text)
{
}
bool UTF8Reader::is_end()
{
return current_position >= length;
}
unsigned int UTF8Reader::character()
{
if (current_position >= length)
return 0;
int trailing_bytes = trailing_bytes_for_utf8[data[current_position]];
if (trailing_bytes == 0 && (data[current_position] & 0x80) == 0x80)
return '?';
if (current_position + 1 + trailing_bytes > length)
{
return '?';
}
else
{
unsigned int ucs4 = (data[current_position] & bitmask_leadbyte_for_utf8[trailing_bytes]);
for (std::string::size_type i = 0; i < trailing_bytes; i++)
{
if ((data[current_position + 1 + i] & 0xC0) == 0x80)
ucs4 = (ucs4 << 6) + (data[current_position + 1 + i] & 0x3f);
else
return '?';
}
// To do: verify that the ucs4 value is in the range for the trailing_bytes specified in the lead byte.
return ucs4;
}
}
std::string::size_type UTF8Reader::char_length()
{
if (current_position < length)
{
int trailing_bytes = trailing_bytes_for_utf8[data[current_position]];
if (current_position + 1 + trailing_bytes > length)
return 1;
for (std::string::size_type i = 0; i < trailing_bytes; i++)
{
if ((data[current_position + 1 + i] & 0xC0) != 0x80)
return 1;
}
return 1 + trailing_bytes;
}
else
{
return 0;
}
}
void UTF8Reader::prev()
{
if (current_position > length)
current_position = length;
if (current_position > 0)
{
current_position--;
move_to_leadbyte();
}
}
void UTF8Reader::next()
{
current_position += char_length();
}
void UTF8Reader::move_to_leadbyte()
{
if (current_position < length)
{
int lead_position = (int)current_position;
while (lead_position > 0 && (data[lead_position] & 0xC0) == 0x80)
lead_position--;
int trailing_bytes = trailing_bytes_for_utf8[data[lead_position]];
if (lead_position + trailing_bytes >= current_position)
current_position = lead_position;
}
}
std::string::size_type UTF8Reader::position()
{
return current_position;
}
void UTF8Reader::set_position(std::string::size_type position)
{
current_position = position;
}

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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