Загрузить файлы в «/»
This commit is contained in:
parent
00e372a571
commit
6fc470d37a
BIN
Terminus.ttf
Normal file
BIN
Terminus.ttf
Normal file
Binary file not shown.
732
gui.cpp
Normal file
732
gui.cpp
Normal file
@ -0,0 +1,732 @@
|
||||
#include "gui.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <chrono>
|
||||
|
||||
namespace gui
|
||||
{
|
||||
static const int ROW = 20;
|
||||
static float ease(float t) { return t * t * (3 - 2 * t); }
|
||||
static XRectangle intersect(const XRectangle& a, const XRectangle& b) {
|
||||
int x1 = std::max((int)a.x, (int)b.x), y1 = std::max((int)a.y, (int)b.y);
|
||||
int x2 = std::min((int)a.x + a.width, (int)b.x + b.width);
|
||||
int y2 = std::min((int)a.y + a.height, (int)b.y + b.height);
|
||||
if (x2 <= x1 || y2 <= y1) return {0, 0, 0, 0};
|
||||
return {(short)x1, (short)y1, (unsigned short)(x2 - x1), (unsigned short)(y2 - y1)};
|
||||
}
|
||||
static void set_clip(Display* d, GC gc, const XRectangle& r) {
|
||||
XRectangle rr = r;
|
||||
XSetClipRectangles(d, gc, 0, 0, &rr, 1, Unsorted);
|
||||
}
|
||||
|
||||
EventBus& EventBus::get() { static EventBus inst; return inst; }
|
||||
int EventBus::on(const std::string& e, Handler h) {
|
||||
handlers[e].push_back({next_token, std::move(h)});
|
||||
return next_token++;
|
||||
}
|
||||
void EventBus::off(int t) {
|
||||
for (auto& [name, vec] : handlers)
|
||||
vec.erase(std::remove_if(vec.begin(), vec.end(),
|
||||
[t](auto& p){ return p.first == t; }), vec.end());
|
||||
}
|
||||
void EventBus::emit(const std::string& e, EventData d) {
|
||||
auto it = handlers.find(e);
|
||||
if (it == handlers.end()) return;
|
||||
auto copy = it->second; // безопасно при off() внутри хендлера
|
||||
for (auto& [t, h] : copy) h(d);
|
||||
}
|
||||
|
||||
// ================= RawPicture =================
|
||||
RawPicture::RawPicture(int w, int h, int bits) { create(w, h, bits); }
|
||||
void RawPicture::create(int W, int H, int b) {
|
||||
w = W; h = H; bits = (b == 8 || b == 16 || b == 24) ? b : 32;
|
||||
buf.assign((size_t)w * h * bpp(), 0);
|
||||
}
|
||||
void RawPicture::setPixel(int x, int y, uint32_t c) {
|
||||
if (x < 0 || y < 0 || x >= w || y >= h) return;
|
||||
uint8_t r = (c >> 16) & 255, g = (c >> 8) & 255, b = c & 255;
|
||||
uint8_t* p = &buf[(size_t)(y * w + x) * bpp()];
|
||||
switch (bits) {
|
||||
case 8: p[0] = (uint8_t)((r * 299 + g * 587 + b * 114) / 1000); break;
|
||||
case 16: { uint16_t v = (uint16_t)(((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3));
|
||||
p[0] = v & 255; p[1] = v >> 8; break; }
|
||||
case 24: p[0] = r; p[1] = g; p[2] = b; break;
|
||||
default: p[0] = r; p[1] = g; p[2] = b; p[3] = 255; break;
|
||||
}
|
||||
}
|
||||
uint32_t RawPicture::getPixel(int x, int y) const {
|
||||
if (x < 0 || y < 0 || x >= w || y >= h) return 0;
|
||||
const uint8_t* p = &buf[(size_t)(y * w + x) * bpp()];
|
||||
switch (bits) {
|
||||
case 8: return ((uint32_t)p[0] << 16) | ((uint32_t)p[0] << 8) | p[0];
|
||||
case 16: { uint16_t v = (uint16_t)(p[0] | (p[1] << 8));
|
||||
uint32_t r = (v >> 11) & 31, g = (v >> 5) & 63, b = v & 31;
|
||||
return (r << 19 | r << 14) & 0xFF0000 | (g << 10 | g << 4) & 0xFF00 | (b << 3 | b >> 2); }
|
||||
default: return ((uint32_t)p[0] << 16) | ((uint32_t)p[1] << 8) | p[2];
|
||||
}
|
||||
}
|
||||
void RawPicture::clear(uint32_t c) { for (int y = 0; y < h; y++) for (int x = 0; x < w; x++) setPixel(x, y, c); }
|
||||
void RawPicture::drawLine(int x0, int y0, int x1, int y1, uint32_t c) {
|
||||
int dx = std::abs(x1 - x0), dy = -std::abs(y1 - y0);
|
||||
int sx = x0 < x1 ? 1 : -1, sy = y0 < y1 ? 1 : -1, err = dx + dy;
|
||||
while (true) {
|
||||
setPixel(x0, y0, c);
|
||||
if (x0 == x1 && y0 == y1) break;
|
||||
int e2 = 2 * err;
|
||||
if (e2 >= dy) { err += dy; x0 += sx; }
|
||||
if (e2 <= dx) { err += dx; y0 += sy; }
|
||||
}
|
||||
}
|
||||
void RawPicture::drawRect(int x, int y, int W, int H, uint32_t c) {
|
||||
drawLine(x, y, x + W - 1, y, c); drawLine(x, y + H - 1, x + W - 1, y + H - 1, c);
|
||||
drawLine(x, y, x, y + H - 1, c); drawLine(x + W - 1, y, x + W - 1, y + H - 1, c);
|
||||
}
|
||||
void RawPicture::fillRect(int x, int y, int W, int H, uint32_t c) {
|
||||
for (int j = y; j < y + H; j++) for (int i = x; i < x + W; i++) setPixel(i, j, c);
|
||||
}
|
||||
void RawPicture::drawCircle(int cx, int cy, int r, uint32_t c) {
|
||||
int x = r, y = 0, err = 0;
|
||||
while (x >= y) {
|
||||
setPixel(cx + x, cy + y, c); setPixel(cx + y, cy + x, c);
|
||||
setPixel(cx - y, cy + x, c); setPixel(cx - x, cy + y, c);
|
||||
setPixel(cx - x, cy - y, c); setPixel(cx - y, cy - x, c);
|
||||
setPixel(cx + y, cy - x, c); setPixel(cx + x, cy - y, c);
|
||||
y++; err += 1 + 2 * y;
|
||||
if (2 * (err - x) + 1 > 0) { x--; err += 1 - 2 * x; }
|
||||
}
|
||||
}
|
||||
void RawPicture::fillCircle(int cx, int cy, int r, uint32_t c) {
|
||||
for (int dy = -r; dy <= r; dy++) {
|
||||
int dx = (int)std::sqrt((double)r * r - dy * dy);
|
||||
for (int x = cx - dx; x <= cx + dx; x++) setPixel(x, cy + dy, c);
|
||||
}
|
||||
}
|
||||
void RawPicture::fillPolygon(const std::vector<std::pair<int,int>>& pts, uint32_t c) {
|
||||
if (pts.size() < 3) return;
|
||||
int miny = 1 << 30, maxy = -(1 << 30);
|
||||
for (auto& p : pts) { miny = std::min(miny, p.second); maxy = std::max(maxy, p.second); }
|
||||
for (int y = std::max(0, miny); y <= std::min(h - 1, maxy); y++) {
|
||||
std::vector<int> xs;
|
||||
size_t n = pts.size();
|
||||
for (size_t i = 0, j = n - 1; i < n; j = i++) {
|
||||
int y0 = pts[i].second, y1 = pts[j].second;
|
||||
if ((y0 > y) != (y1 > y)) {
|
||||
int x0 = pts[i].first, x1 = pts[j].first;
|
||||
xs.push_back(x0 + (int)((double)(y - y0) / (y1 - y0) * (x1 - x0)));
|
||||
}
|
||||
}
|
||||
std::sort(xs.begin(), xs.end());
|
||||
for (size_t i = 0; i + 1 < xs.size(); i += 2)
|
||||
for (int x = xs[i]; x <= xs[i + 1]; x++) setPixel(x, y, c);
|
||||
}
|
||||
}
|
||||
void RawPicture::drawPolygon(const std::vector<std::pair<int,int>>& pts, uint32_t c) {
|
||||
for (size_t i = 0; i < pts.size(); i++) {
|
||||
auto& a = pts[i]; auto& b = pts[(i + 1) % pts.size()];
|
||||
drawLine(a.first, a.second, b.first, b.second, c);
|
||||
}
|
||||
}
|
||||
void RawPicture::drawTriangle(int x1,int y1,int x2,int y2,int x3,int y3,uint32_t c) {
|
||||
drawLine(x1,y1,x2,y2,c); drawLine(x2,y2,x3,y3,c); drawLine(x3,y3,x1,y1,c);
|
||||
}
|
||||
void RawPicture::fillTriangle(int x1,int y1,int x2,int y2,int x3,int y3,uint32_t c) {
|
||||
fillPolygon({{x1,y1},{x2,y2},{x3,y3}}, c);
|
||||
}
|
||||
void RawPicture::drawText(Display* dpy, int x, int y, const std::string& t, uint32_t c, int scale) {
|
||||
if (!dpy || t.empty()) return;
|
||||
int scr = DefaultScreen(dpy);
|
||||
|
||||
static Font f = 0;
|
||||
if (!f) {
|
||||
const char* names[] = {"fixed", "-*-fixed-*-*-*-*-*-*-*-*-*-*-*", "6x13", "7x13", nullptr};
|
||||
for (int i = 0; names[i] && !f; i++) f = XLoadFont(dpy, names[i]);
|
||||
}
|
||||
if (!f) {
|
||||
for (size_t i = 0; i < t.size(); i++)
|
||||
fillRect(x + (int)i * 6 * scale, y, 5 * scale, 8 * scale, c);
|
||||
return;
|
||||
}
|
||||
|
||||
XFontStruct* fs = XQueryFont(dpy, f);
|
||||
if (!fs) return; // защита от NULL
|
||||
|
||||
int cw = fs->max_bounds.width ? fs->max_bounds.width : 6;
|
||||
int ch = fs->ascent + fs->descent;
|
||||
if (cw <= 0 || ch <= 0) { XFreeFont(dpy, fs); return; }
|
||||
|
||||
int tw = cw * (int)t.size();
|
||||
Pixmap pm = XCreatePixmap(dpy, RootWindow(dpy, scr), tw, ch, DefaultDepth(dpy, scr));
|
||||
GC g = XCreateGC(dpy, pm, 0, nullptr);
|
||||
XSetForeground(dpy, g, 0); XFillRectangle(dpy, pm, g, 0, 0, tw, ch);
|
||||
XSetForeground(dpy, g, 0xFFFFFF); XSetFont(dpy, g, f);
|
||||
XDrawString(dpy, pm, g, 0, fs->ascent, t.c_str(), (int)t.size());
|
||||
|
||||
XImage* im = XGetImage(dpy, pm, 0, 0, tw, ch, AllPlanes, ZPixmap);
|
||||
if (im) {
|
||||
for (int py = 0; py < ch; py++)
|
||||
for (int px = 0; px < tw; px++)
|
||||
if (XGetPixel(im, px, py))
|
||||
for (int sy = 0; sy < scale; sy++)
|
||||
for (int sx = 0; sx < scale; sx++)
|
||||
setPixel(x + px * scale + sx, y + py * scale + sy, c);
|
||||
XDestroyImage(im);
|
||||
}
|
||||
XFreeGC(dpy, g); XFreePixmap(dpy, pm); XFreeFont(dpy, fs);
|
||||
}
|
||||
void RawPicture::blit(const RawPicture& src, int dx, int dy) {
|
||||
for (int y = 0; y < src.h; y++)
|
||||
for (int x = 0; x < src.w; x++)
|
||||
setPixel(dx + x, dy + y, src.getPixel(x, y));
|
||||
}
|
||||
bool RawPicture::load(const std::string& path) {
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
if (!file) return false;
|
||||
std::vector<uint8_t> d((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||
|
||||
if (d.size() > 54 && d[0] == 'B' && d[1] == 'M') { // ---- BMP ----
|
||||
uint32_t off = *(uint32_t*)&d[10];
|
||||
int32_t W = *(int32_t*)&d[18], H = *(int32_t*)&d[22];
|
||||
uint16_t bppv = *(uint16_t*)&d[28];
|
||||
if (bppv != 24 && bppv != 32) return false;
|
||||
create(W, std::abs(H), bits);
|
||||
bool flip = H > 0;
|
||||
int row_sz = ((W * (bppv / 8) + 3) / 4) * 4;
|
||||
for (int y = 0; y < h; y++) {
|
||||
int sy = flip ? (h - 1 - y) : y;
|
||||
const uint8_t* row = &d[off + sy * row_sz];
|
||||
for (int x = 0; x < w; x++) {
|
||||
const uint8_t* px = row + x * (bppv / 8);
|
||||
setPixel(x, y, ((uint32_t)px[2] << 16) | ((uint32_t)px[1] << 8) | px[0]);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (d.size() > 2 && d[0] == 'P' && d[1] == '6') { // ---- PPM P6 ----
|
||||
size_t pos = 2; int vals[3] = {0, 0, 0}, got = 0;
|
||||
while (got < 3 && pos < d.size()) {
|
||||
while (pos < d.size() && std::isspace(d[pos])) pos++;
|
||||
if (d[pos] == '#') { while (pos < d.size() && d[pos] != '\n') pos++; continue; }
|
||||
int v = 0;
|
||||
while (pos < d.size() && std::isdigit(d[pos])) v = v * 10 + (d[pos++] - '0');
|
||||
if (v) vals[got++] = v;
|
||||
}
|
||||
pos++;
|
||||
create(vals[0], vals[1], bits);
|
||||
for (int y = 0; y < h && pos + 2 < d.size(); y++)
|
||||
for (int x = 0; x < w; x++, pos += 3)
|
||||
setPixel(x, y, ((uint32_t)d[pos] << 16) | ((uint32_t)d[pos + 1] << 8) | d[pos + 2]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void RawPicture::present(Display* dpy, Window win, GC gc, int dx, int dy) const {
|
||||
if (w <= 0 || h <= 0) return;
|
||||
int scr = DefaultScreen(dpy);
|
||||
char* data = (char*)malloc((size_t)w * h * 4);
|
||||
uint32_t* px = (uint32_t*)data;
|
||||
for (int y = 0; y < h; y++)
|
||||
for (int x = 0; x < w; x++) px[y * w + x] = getPixel(x, y);
|
||||
XImage* im = XCreateImage(dpy, DefaultVisual(dpy, scr), DefaultDepth(dpy, scr),
|
||||
ZPixmap, 0, data, (unsigned)w, (unsigned)h, 32, w * 4);
|
||||
XPutImage(dpy, win, gc, im, 0, 0, dx, dy, (unsigned)w, (unsigned)h);
|
||||
XDestroyImage(im);
|
||||
}
|
||||
|
||||
Widget& combat::add_widget(WidgetData d) {
|
||||
auto w = std::make_unique<Widget>();
|
||||
w->data = std::move(d);
|
||||
widgets.push_back(std::move(w));
|
||||
return *widgets.back();
|
||||
}
|
||||
Widget& combat::Button(WidgetData a) { a.type = WidgetType::BUTTON; return add_widget(std::move(a)); }
|
||||
Widget& combat::Label(WidgetData a) { a.type = WidgetType::LABEL; return add_widget(std::move(a)); }
|
||||
Widget& combat::Input(WidgetData a) { a.type = WidgetType::INPUT; return add_widget(std::move(a)); }
|
||||
Widget& combat::Checkbox(WidgetData a) { a.type = WidgetType::CHECKBOX; return add_widget(std::move(a)); }
|
||||
Widget& combat::Radiobutton(WidgetData a) { a.type = WidgetType::RADIO; return add_widget(std::move(a)); }
|
||||
Widget& combat::Slider(WidgetData a) { a.type = WidgetType::SLIDER; return add_widget(std::move(a)); }
|
||||
Widget& combat::Progressbar(WidgetData a) { a.type = WidgetType::PROGRESS; return add_widget(std::move(a)); }
|
||||
Widget& combat::Listbox(WidgetData a) { a.type = WidgetType::LISTBOX; return add_widget(std::move(a)); }
|
||||
Widget& combat::Dropdown(WidgetData a) { a.type = WidgetType::DROPDOWN; return add_widget(std::move(a)); }
|
||||
Widget& combat::TreeView(WidgetData a) { a.type = WidgetType::TREE_VIEW; return add_widget(std::move(a)); }
|
||||
Widget& combat::TreeNode(WidgetData a) { a.type = WidgetType::TREE; return add_widget(std::move(a)); }
|
||||
Widget& combat::ScrollView(WidgetData a) { a.type = WidgetType::SCROLL; return add_widget(std::move(a)); }
|
||||
Widget& combat::RawPictureWidget(WidgetData a) { a.type = WidgetType::RAW_PIC; return add_widget(std::move(a)); }
|
||||
Widget& combat::SpriteButton(WidgetData a){ a.type = WidgetType::SPRITE_BTN; return add_widget(std::move(a)); }
|
||||
|
||||
void combat::visit(const std::function<void(Widget&)>& fn) {
|
||||
for (auto& w : widgets) { fn(*w); w->visit(fn); }
|
||||
}
|
||||
Widget* combat::find(const std::string& id) {
|
||||
Widget* res = nullptr;
|
||||
visit([&](Widget& w){ if (!res && w.data.id == id) res = &w; });
|
||||
return res;
|
||||
}
|
||||
void combat::tick(float dt) { for (auto& w : widgets) w->tick(dt); }
|
||||
void combat::render_all(Display* d, Window win, GC gc, int ox, int oy, const XRectangle& clip, OverlayList* ov) const {
|
||||
for (auto& w : widgets) w->draw(d, win, gc, ox, oy, clip, ov);
|
||||
}
|
||||
int combat::process_press(int mx, int my, window& w, page& p, int ox, int oy) {
|
||||
for (auto it = widgets.rbegin(); it != widgets.rend(); ++it) {
|
||||
int r = (*it)->handle_press(mx, my, w, p, ox, oy);
|
||||
if (r) return r;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
bool combat::process_wheel(int delta, int mx, int my, int ox, int oy) {
|
||||
for (auto it = widgets.rbegin(); it != widgets.rend(); ++it)
|
||||
if ((*it)->handle_wheel(delta, mx, my, ox, oy)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Widget::contains(int mx, int my, int ox, int oy) const {
|
||||
int ax = data.x + ox, ay = data.y + oy;
|
||||
return mx >= ax && mx <= ax + data.width && my >= ay && my <= ay + data.height;
|
||||
}
|
||||
|
||||
int Widget::content_height() const {
|
||||
int ch = 0;
|
||||
if (data.type == WidgetType::DROPDOWN) {
|
||||
ch = (int)data.items.size() * ROW;
|
||||
for (auto& c : widgets) ch += c->data.height + 2;
|
||||
} else if (data.type == WidgetType::TREE_VIEW) {
|
||||
// рекурсивный подсчёт строк дерева
|
||||
std::function<int(const Widget&)> calc = [&](const Widget& n) -> int {
|
||||
int s = ROW;
|
||||
if (n.data.expanded)
|
||||
for (auto& c : n.widgets)
|
||||
s += (c->data.type == WidgetType::TREE) ? calc(*c) : c->data.height + 2;
|
||||
return s;
|
||||
};
|
||||
for (auto& c : widgets) ch += calc(*c);
|
||||
} else {
|
||||
ch = (int)data.items.size() * ROW;
|
||||
for (auto& c : widgets) ch = std::max(ch, c->data.y + c->data.height);
|
||||
}
|
||||
return ch + 4;
|
||||
}
|
||||
|
||||
Widget* combat::find_at(int mx, int my) {
|
||||
Widget* res = nullptr;
|
||||
visit([&](Widget& w){ if (w.contains(mx, my, 0, 0)) res = &w; });
|
||||
return res;
|
||||
}
|
||||
|
||||
void Widget::tick(float dt) {
|
||||
if (data.type == WidgetType::DROPDOWN) {
|
||||
float target = open_target ? 1.f : 0.f;
|
||||
anim += (target > anim ? dt * 10 : -dt * 10);
|
||||
anim = std::clamp(anim, 0.f, 1.f);
|
||||
}
|
||||
if (data.type == WidgetType::PROGRESS) {
|
||||
if (data.indeterminate) {
|
||||
phase += dt * 0.9f;
|
||||
data.value = std::fmod(phase, 1.3f) - 0.15f;
|
||||
} else {
|
||||
data.value += (data.target_value - data.value) * std::min(1.f, dt * 8);
|
||||
if (std::abs(data.target_value - data.value) < 0.001f) data.value = data.target_value;
|
||||
}
|
||||
}
|
||||
combat::tick(dt);
|
||||
}
|
||||
|
||||
void Widget::draw(Display* d, Window win, GC gc, int ox, int oy, const XRectangle& clip, OverlayList* ov) const {
|
||||
int ax = data.x + ox, ay = data.y + oy;
|
||||
XSetForeground(d, gc, data.bg_color);
|
||||
XFillRectangle(d, win, gc, ax, ay, data.width, data.height);
|
||||
|
||||
switch (data.type) {
|
||||
case WidgetType::LABEL:
|
||||
XSetForeground(d, gc, data.fg_color);
|
||||
XDrawString(d, win, gc, ax, ay + 12, data.text.c_str(), (int)data.text.size());
|
||||
break;
|
||||
case WidgetType::BUTTON: case WidgetType::INPUT: case WidgetType::SPRITE_BTN: {
|
||||
if (data.picture) data.picture->present(d, win, gc, ax, ay);
|
||||
XSetForeground(d, gc, data.frame_color);
|
||||
XDrawRectangle(d, win, gc, ax, ay, data.width, data.height);
|
||||
XSetForeground(d, gc, data.fg_color);
|
||||
XDrawString(d, win, gc, ax + 5, ay + data.height / 2 + 5, data.text.c_str(), (int)data.text.size());
|
||||
if (data.type == WidgetType::INPUT)
|
||||
XDrawLine(d, win, gc, ax + 5 + (int)data.text.size() * 7, ay + data.height - 6,
|
||||
ax + 13 + (int)data.text.size() * 7, ay + data.height - 6);
|
||||
break;
|
||||
}
|
||||
case WidgetType::CHECKBOX: {
|
||||
int bs = data.height - 6;
|
||||
XSetForeground(d, gc, data.frame_color);
|
||||
XDrawRectangle(d, win, gc, ax + 3, ay + 3, bs, bs);
|
||||
if (data.checked) {
|
||||
XDrawLine(d, win, gc, ax + 5, ay + 5, ax + bs + 1, ay + bs + 1);
|
||||
XDrawLine(d, win, gc, ax + bs + 1, ay + 5, ax + 5, ay + bs + 1);
|
||||
}
|
||||
XSetForeground(d, gc, data.fg_color);
|
||||
XDrawString(d, win, gc, ax + bs + 10, ay + data.height / 2 + 5, data.text.c_str(), (int)data.text.size());
|
||||
break;
|
||||
}
|
||||
case WidgetType::RADIO: {
|
||||
int r = (data.height - 6) / 2, cx = ax + r + 3, cy = ay + data.height / 2;
|
||||
XSetForeground(d, gc, data.frame_color);
|
||||
XDrawArc(d, win, gc, cx - r, cy - r, r * 2, r * 2, 0, 360 * 64);
|
||||
if (data.checked) { int ir = r / 2; XFillArc(d, win, gc, cx - ir, cy - ir, ir * 2, ir * 2, 0, 360 * 64); }
|
||||
XSetForeground(d, gc, data.fg_color);
|
||||
XDrawString(d, win, gc, ax + r * 2 + 10, ay + data.height / 2 + 5, data.text.c_str(), (int)data.text.size());
|
||||
break;
|
||||
}
|
||||
case WidgetType::SLIDER: {
|
||||
int ty = ay + data.height / 2 - 2;
|
||||
XSetForeground(d, gc, 0x444444);
|
||||
XFillRectangle(d, win, gc, ax, ty, data.width, 4);
|
||||
XSetForeground(d, gc, data.frame_color);
|
||||
XFillRectangle(d, win, gc, ax, ty, (int)(data.width * data.value), 4);
|
||||
int kx = ax + (int)(data.value * (data.width - 10));
|
||||
XFillRectangle(d, win, gc, kx, ay + 2, 10, data.height - 4);
|
||||
break;
|
||||
}
|
||||
case WidgetType::PROGRESS: {
|
||||
XSetForeground(d, gc, data.frame_color);
|
||||
XDrawRectangle(d, win, gc, ax, ay, data.width, data.height);
|
||||
float v = std::clamp(data.value, 0.f, 1.f);
|
||||
XFillRectangle(d, win, gc, ax + 1, ay + 1, (int)((data.width - 2) * v), data.height - 2);
|
||||
XSetForeground(d, gc, data.fg_color);
|
||||
std::string pct = std::to_string((int)(v * 100)) + "%";
|
||||
XDrawString(d, win, gc, ax + data.width / 2 - 10, ay + data.height / 2 + 5, pct.c_str(), (int)pct.size());
|
||||
break;
|
||||
}
|
||||
case WidgetType::LISTBOX: case WidgetType::SCROLL: {
|
||||
XSetForeground(d, gc, data.frame_color);
|
||||
XDrawRectangle(d, win, gc, ax, ay, data.width, data.height);
|
||||
XRectangle vp = intersect(clip, {(short)(ax + 1), (short)(ay + 1),
|
||||
(unsigned short)(data.width - 2), (unsigned short)(data.height - 2)});
|
||||
set_clip(d, gc, vp);
|
||||
int cy = ay + 1 - data.scroll;
|
||||
for (size_t i = 0; i < data.items.size(); i++) {
|
||||
if ((int)i == data.selected_index) {
|
||||
XSetForeground(d, gc, data.frame_color);
|
||||
XFillRectangle(d, win, gc, ax + 1, cy, data.width - 2, ROW);
|
||||
XSetForeground(d, gc, 0x000000);
|
||||
} else XSetForeground(d, gc, data.fg_color);
|
||||
XDrawString(d, win, gc, ax + 5, cy + 15, data.items[i].c_str(), (int)data.items[i].size());
|
||||
cy += ROW;
|
||||
}
|
||||
render_all(d, win, gc, ax + 1, ay + 1 - data.scroll, vp, ov);
|
||||
set_clip(d, gc, clip);
|
||||
// скроллбар
|
||||
int chh = content_height();
|
||||
if (chh > data.height) {
|
||||
int track = data.height - 4;
|
||||
int thumb = std::max(15, track * data.height / chh);
|
||||
int ty = ay + 2 + (int)((float)data.scroll / (chh - data.height) * (track - thumb));
|
||||
XSetForeground(d, gc, 0x555555);
|
||||
XFillRectangle(d, win, gc, ax + data.width - 6, ty, 4, thumb);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WidgetType::TREE_VIEW: {
|
||||
XSetForeground(d, gc, data.frame_color);
|
||||
XDrawRectangle(d, win, gc, ax, ay, data.width, data.height);
|
||||
XRectangle vp = intersect(clip, {(short)(ax + 1), (short)(ay + 1),
|
||||
(unsigned short)(data.width - 2), (unsigned short)(data.height - 2)});
|
||||
set_clip(d, gc, vp);
|
||||
std::function<int(const Widget&, int, int)> drawN = [&](const Widget& n, int depth, int y) -> int {
|
||||
int nx = ax + 4 + depth * 14;
|
||||
XSetForeground(d, gc, data.frame_color);
|
||||
XDrawString(d, win, gc, nx, y + 15, n.data.expanded ? "[-]" : "[+]", 3);
|
||||
XSetForeground(d, gc, data.fg_color);
|
||||
XDrawString(d, win, gc, nx + 22, y + 15, n.data.text.c_str(), (int)n.data.text.size());
|
||||
y += ROW;
|
||||
if (n.data.expanded)
|
||||
for (auto& c : n.widgets) {
|
||||
if (c->data.type == WidgetType::TREE) y = drawN(*c, depth + 1, y);
|
||||
else { c->draw(d, win, gc, nx + 22, y - data.scroll + 0, vp, ov); y += c->data.height + 2; }
|
||||
}
|
||||
return y;
|
||||
};
|
||||
int y = ay + 2 - data.scroll;
|
||||
for (auto& c : widgets) y = drawN(*c, 0, y);
|
||||
set_clip(d, gc, clip);
|
||||
break;
|
||||
}
|
||||
case WidgetType::DROPDOWN: {
|
||||
XSetForeground(d, gc, data.frame_color);
|
||||
XDrawRectangle(d, win, gc, ax, ay, data.width, data.height);
|
||||
XSetForeground(d, gc, data.fg_color);
|
||||
std::string disp = (data.selected_index >= 0 && data.selected_index < (int)data.items.size())
|
||||
? data.items[data.selected_index] : data.text;
|
||||
XDrawString(d, win, gc, ax + 5, ay + data.height / 2 + 5, disp.c_str(), (int)disp.size());
|
||||
XDrawString(d, win, gc, ax + data.width - 14, ay + data.height / 2 + 5, "v", 1);
|
||||
if (ov && anim > 0.01f) ov->push_back(this); // рисуется ПОСЛЕ всех, поверх
|
||||
break;
|
||||
}
|
||||
case WidgetType::RAW_PIC:
|
||||
if (data.picture) data.picture->present(d, win, gc, ax, ay);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
void Widget::draw_popup(Display* d, Window win, GC gc, const XRectangle& clip) const {
|
||||
int ax = data.x, ay = data.y + data.height;
|
||||
int total = content_height();
|
||||
int hgt = (int)(total * ease(anim));
|
||||
if (hgt <= 0) return;
|
||||
XRectangle vp = intersect(clip, {(short)ax, (short)ay, (unsigned short)data.width, (unsigned short)hgt});
|
||||
set_clip(d, gc, vp);
|
||||
XSetForeground(d, gc, 0x222222);
|
||||
XFillRectangle(d, win, gc, ax, ay, data.width, hgt);
|
||||
XSetForeground(d, gc, data.frame_color);
|
||||
XDrawRectangle(d, win, gc, ax, ay, data.width, hgt);
|
||||
int cy = ay;
|
||||
for (size_t i = 0; i < data.items.size(); i++) {
|
||||
if ((int)i == data.selected_index) {
|
||||
XSetForeground(d, gc, data.frame_color);
|
||||
XFillRectangle(d, win, gc, ax + 1, cy, data.width - 2, ROW);
|
||||
XSetForeground(d, gc, 0x000000);
|
||||
} else XSetForeground(d, gc, data.fg_color);
|
||||
XDrawString(d, win, gc, ax + 5, cy + 15, data.items[i].c_str(), (int)data.items[i].size());
|
||||
cy += ROW;
|
||||
}
|
||||
for (auto& c : widgets) { c->draw(d, win, gc, ax + 1, cy, vp, nullptr); cy += c->data.height + 2; }
|
||||
set_clip(d, gc, clip);
|
||||
}
|
||||
|
||||
int Widget::handle_press(int mx, int my, window& w, page& p, int ox, int oy) {
|
||||
int ax = data.x + ox, ay = data.y + oy;
|
||||
|
||||
if (data.type == WidgetType::DROPDOWN) {
|
||||
if (open_target && mx >= ax && mx <= ax + data.width &&
|
||||
my >= ay + data.height && my <= ay + data.height + content_height()) {
|
||||
int cy = ay + data.height;
|
||||
for (size_t i = 0; i < data.items.size(); i++, cy += ROW)
|
||||
if (my >= cy && my < cy + ROW) {
|
||||
data.selected_index = (int)i; open_target = false;
|
||||
EventBus::get().emit("dropdown.select", {data.id, data.items[i], 0, (int)i, false});
|
||||
if (data.on_change) data.on_change();
|
||||
return 1;
|
||||
}
|
||||
for (auto& c : widgets) {
|
||||
int r = c->handle_press(mx, my, w, p, ax + 1, cy);
|
||||
if (r) { open_target = false; return r; }
|
||||
cy += c->data.height + 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (contains(mx, my, ox, oy)) { open_target = !open_target; return 1; }
|
||||
if (open_target) { open_target = false; return 1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (data.type == WidgetType::LISTBOX || data.type == WidgetType::SCROLL) {
|
||||
if (!contains(mx, my, ox, oy)) return 0;
|
||||
int r = process_press(mx, my, w, p, ax + 1, ay + 1 - data.scroll);
|
||||
if (r) return r;
|
||||
if (data.type == WidgetType::LISTBOX) {
|
||||
int idx = (my - ay + data.scroll) / ROW;
|
||||
if (idx >= 0 && idx < (int)data.items.size()) {
|
||||
data.selected_index = idx;
|
||||
EventBus::get().emit("list.select", {data.id, data.items[idx], 0, idx, false});
|
||||
if (data.on_change) data.on_change();
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (data.type == WidgetType::TREE_VIEW) {
|
||||
if (!contains(mx, my, ox, oy)) return 0;
|
||||
std::function<int(Widget&, int, int)> hitN = [&](Widget& n, int depth, int y) -> int {
|
||||
int nx = ax + 4 + depth * 14;
|
||||
if (my >= y && my < y + ROW && mx >= nx && mx < nx + 22 + 8 * 14) {
|
||||
if (mx < nx + 20) {
|
||||
n.data.expanded = !n.data.expanded;
|
||||
EventBus::get().emit("tree.toggle", {n.data.id, n.data.text, 0, 0, n.data.expanded});
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
y += ROW;
|
||||
if (n.data.expanded)
|
||||
for (auto& c : n.widgets) {
|
||||
if (c->data.type == WidgetType::TREE) { int r = hitN(*c, depth + 1, y); if (r) return r;
|
||||
y += c->content_height(); }
|
||||
else { int r = c->handle_press(mx, my, w, p, nx + 22, y); if (r) return r; y += c->data.height + 2; }
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
int y = ay + 2 - data.scroll;
|
||||
for (auto& c : widgets) { int r = hitN(*c, 0, y); if (r) return r; y += c->content_height(); }
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (data.type == WidgetType::SLIDER && contains(mx, my, ox, oy)) {
|
||||
data.value = std::clamp((float)(mx - ax) / data.width, 0.f, 1.f);
|
||||
data.target_value = data.value;
|
||||
cap_ox = ox; cap_oy = oy;
|
||||
EventBus::get().emit("slider.change", {data.id, "", data.value, 0, false});
|
||||
if (data.on_change) data.on_change();
|
||||
return 2; // захват мыши для drag
|
||||
}
|
||||
|
||||
if (!contains(mx, my, ox, oy)) return 0;
|
||||
switch (data.type) {
|
||||
case WidgetType::BUTTON: case WidgetType::SPRITE_BTN:
|
||||
EventBus::get().emit("button.click", {data.id, data.text, 0, 0, false});
|
||||
if (data.on_page_click) data.on_page_click(p);
|
||||
if (data.on_window_click) data.on_window_click(w);
|
||||
return 1;
|
||||
case WidgetType::INPUT: w.set_focus(this); return 1;
|
||||
case WidgetType::CHECKBOX:
|
||||
data.checked = !data.checked;
|
||||
EventBus::get().emit("checkbox.change", {data.id, data.text, 0, 0, data.checked});
|
||||
if (data.on_change) data.on_change();
|
||||
return 1;
|
||||
case WidgetType::RADIO:
|
||||
if (!data.checked) {
|
||||
p.visit([&](Widget& x){ if (x.data.type == WidgetType::RADIO && x.data.id == data.id) x.data.checked = false; });
|
||||
data.checked = true;
|
||||
EventBus::get().emit("radio.change", {data.id, data.text, 0, 0, true});
|
||||
if (data.on_change) data.on_change();
|
||||
}
|
||||
return 1;
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
void Widget::handle_drag(int mx, int my) {
|
||||
if (data.type == WidgetType::SLIDER) {
|
||||
int ax = data.x + cap_ox;
|
||||
data.value = std::clamp((float)(mx - ax) / data.width, 0.f, 1.f);
|
||||
data.target_value = data.value;
|
||||
EventBus::get().emit("slider.change", {data.id, "", data.value, 0, false});
|
||||
if (data.on_change) data.on_change();
|
||||
}
|
||||
}
|
||||
void Widget::handle_release() {}
|
||||
|
||||
bool Widget::handle_wheel(int delta, int mx, int my, int ox, int oy) {
|
||||
int ax = data.x + ox, ay = data.y + oy;
|
||||
switch (data.type) {
|
||||
case WidgetType::LISTBOX: case WidgetType::SCROLL: case WidgetType::TREE_VIEW:
|
||||
if (!contains(mx, my, ox, oy)) return false;
|
||||
if (process_wheel(delta, mx, my, ax + 1, ay + 1 - data.scroll)) return true;
|
||||
data.scroll = std::clamp(data.scroll + delta * 20, 0, std::max(0, content_height() - data.height));
|
||||
return true;
|
||||
case WidgetType::DROPDOWN:
|
||||
if (open_target && contains(mx, my, ox, oy - data.height)) return true;
|
||||
return contains(mx, my, ox, oy) ? true : false;
|
||||
default:
|
||||
if (contains(mx, my, ox, oy)) return process_wheel(delta, mx, my, ax, ay);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
window::window(std::string title) {
|
||||
X11display = XOpenDisplay(NULL);
|
||||
if (!X11display) { std::cerr << "Cannot open display\n"; return; }
|
||||
int s = DefaultScreen(X11display);
|
||||
X11window = XCreateSimpleWindow(X11display, RootWindow(X11display, s), 10, 10, 1200, 600, 1,
|
||||
BlackPixel(X11display, s), 0x121212);
|
||||
XStoreName(X11display, X11window, title.c_str());
|
||||
XSelectInput(X11display, X11window, ExposureMask | KeyPressMask | StructureNotifyMask |
|
||||
ButtonPressMask | ButtonReleaseMask | ButtonMotionMask);
|
||||
XMapWindow(X11display, X11window);
|
||||
gc = XCreateGC(X11display, X11window, 0, NULL);
|
||||
XFlush(X11display);
|
||||
}
|
||||
window::~window() {
|
||||
if (gc) XFreeGC(X11display, gc);
|
||||
if (X11window) XDestroyWindow(X11display, X11window);
|
||||
if (X11display) XCloseDisplay(X11display);
|
||||
}
|
||||
|
||||
void window::update() {
|
||||
if (!X11display) return;
|
||||
static auto last = std::chrono::steady_clock::now();
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
float dt = std::chrono::duration<float>(now - last).count();
|
||||
last = now;
|
||||
|
||||
while (XPending(X11display) > 0) {
|
||||
XEvent ev; XNextEvent(X11display, &ev);
|
||||
handle_event(ev);
|
||||
}
|
||||
for (auto& [n, pg] : pages) pg->tick(dt);
|
||||
|
||||
XClearWindow(X11display, X11window);
|
||||
draw_tabs();
|
||||
draw_active_page();
|
||||
XFlush(X11display);
|
||||
}
|
||||
|
||||
void window::handle_event(const XEvent& ev) {
|
||||
if (ev.type == ButtonPress && ev.xbutton.button == Button1) {
|
||||
int mx = ev.xbutton.x, my = ev.xbutton.y;
|
||||
for (auto& tab : tab_buttons)
|
||||
if (mx >= tab.x && mx <= tab.x + tab.width && my >= tab.y && my <= tab.y + tab.height) {
|
||||
if (tab.on_window_click) tab.on_window_click(*this);
|
||||
return;
|
||||
}
|
||||
if (!current_page_name.empty()) {
|
||||
auto& pg = pages[current_page_name];
|
||||
int r = pg->process_press(mx, my, *this, *pg, 0, 0);
|
||||
if (r == 2) captured = pg->find_at(mx, my); // виджет, захвативший мышь
|
||||
}
|
||||
} else if (ev.type == ButtonRelease) {
|
||||
if (captured) { captured->handle_release(); captured = nullptr; }
|
||||
} else if (ev.type == MotionNotify && captured) {
|
||||
captured->handle_drag(ev.xmotion.x, ev.xmotion.y);
|
||||
} else if (ev.type == ButtonPress && (ev.xbutton.button == Button4 || ev.xbutton.button == Button5)) {
|
||||
int delta = ev.xbutton.button == Button4 ? -1 : 1;
|
||||
if (!current_page_name.empty())
|
||||
pages[current_page_name]->process_wheel(delta, ev.xbutton.x, ev.xbutton.y, 0, 0);
|
||||
} else if (ev.type == KeyPress && focused_input) {
|
||||
char buf[16]; KeySym ks;
|
||||
int len = XLookupString((XKeyEvent*)&ev, buf, sizeof(buf), &ks, nullptr);
|
||||
if (ks == XK_BackSpace) { if (!focused_input->data.text.empty()) focused_input->data.text.pop_back(); }
|
||||
else if (ks == XK_Return) { EventBus::get().emit("input.submit", {focused_input->data.id, focused_input->data.text}); focused_input = nullptr; }
|
||||
else if (ks == XK_Escape) focused_input = nullptr;
|
||||
else if (len > 0 && buf[0] >= 32) focused_input->data.text += buf[0];
|
||||
}
|
||||
}
|
||||
|
||||
void window::draw_tabs() const {
|
||||
for (const auto& b : tab_buttons) {
|
||||
Widget w; w.data = b;
|
||||
XRectangle full{0, 0, 1200, 600};
|
||||
w.draw(X11display, X11window, gc, 0, 0, full, nullptr);
|
||||
}
|
||||
XSetForeground(X11display, gc, 0x333333);
|
||||
XDrawLine(X11display, X11window, gc, 0, 80, 1200, 80);
|
||||
}
|
||||
void window::draw_active_page() const {
|
||||
if (current_page_name.empty()) return;
|
||||
auto it = pages.find(current_page_name);
|
||||
if (it == pages.end()) return;
|
||||
XRectangle full{0, 0, 1200, 600};
|
||||
OverlayList ov;
|
||||
it->second->render_all(X11display, X11window, gc, 0, 0, full, &ov);
|
||||
for (auto* w : ov) w->draw_popup(X11display, X11window, gc, full); // поверх всего
|
||||
}
|
||||
|
||||
void window::addTAB_BUTTON(WidgetData a) {
|
||||
size_t i = tab_buttons.size();
|
||||
a.x = 10; a.y = 10 + (int)i * 35; a.width = 150; a.height = 30;
|
||||
tab_buttons.push_back(std::move(a));
|
||||
}
|
||||
void window::addPAGE(std::string n) { pages[n] = std::make_unique<page>(); }
|
||||
void window::configur_page(std::string n, callback_page fn, bool active) {
|
||||
if (!pages.count(n)) addPAGE(n);
|
||||
pages[n]->active = active;
|
||||
if (active) current_page_name = n;
|
||||
if (fn) fn(*pages[n]);
|
||||
}
|
||||
void window::openPAGE(std::string n) {
|
||||
if (!pages.count(n)) return;
|
||||
for (auto& [k, pg] : pages) pg->active = false;
|
||||
pages[n]->active = true;
|
||||
current_page_name = n;
|
||||
EventBus::get().emit("page.open", {n, n});
|
||||
}
|
||||
Widget* window::find(const std::string& id) {
|
||||
for (auto& [n, pg] : pages) if (auto* w = pg->find(id)) return w;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
211
gui.h
Normal file
211
gui.h
Normal file
@ -0,0 +1,211 @@
|
||||
#ifndef HOST_GUI_H
|
||||
#define HOST_GUI_H
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/keysym.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
|
||||
namespace gui
|
||||
{
|
||||
class window;
|
||||
class page;
|
||||
class RawPicture;
|
||||
struct Widget;
|
||||
|
||||
using callback_void = std::function<void()>;
|
||||
using callback_page = std::function<void(page&)>;
|
||||
using callback_window = std::function<void(window&)>;
|
||||
|
||||
struct EventData {
|
||||
std::string source_id;
|
||||
std::string text;
|
||||
float value = 0;
|
||||
int index = -1;
|
||||
bool flag = false;
|
||||
};
|
||||
|
||||
class EventBus {
|
||||
public:
|
||||
using Handler = std::function<void(const EventData&)>;
|
||||
static EventBus& get();
|
||||
int on(const std::string& event, Handler h);
|
||||
void off(int token);
|
||||
void emit(const std::string& event, EventData data = {});
|
||||
private:
|
||||
int next_token = 1;
|
||||
std::unordered_map<std::string, std::vector<std::pair<int, Handler>>> handlers;
|
||||
};
|
||||
|
||||
class RawPicture {
|
||||
public:
|
||||
RawPicture(int w = 0, int h = 0, int bits = 32);
|
||||
void create(int w, int h, int bits);
|
||||
int width() const { return w; }
|
||||
int height() const { return h; }
|
||||
int bitness() const { return bits; }
|
||||
|
||||
void clear(uint32_t color = 0x000000);
|
||||
void setPixel(int x, int y, uint32_t color);
|
||||
uint32_t getPixel(int x, int y) const;
|
||||
|
||||
void drawLine(int x0, int y0, int x1, int y1, uint32_t c);
|
||||
void drawRect(int x, int y, int w, int h, uint32_t c);
|
||||
void fillRect(int x, int y, int w, int h, uint32_t c);
|
||||
void drawCircle(int cx, int cy, int r, uint32_t c);
|
||||
void fillCircle(int cx, int cy, int r, uint32_t c);
|
||||
void drawTriangle(int x1,int y1,int x2,int y2,int x3,int y3,uint32_t c);
|
||||
void fillTriangle(int x1,int y1,int x2,int y2,int x3,int y3,uint32_t c);
|
||||
void drawPolygon(const std::vector<std::pair<int,int>>& pts, uint32_t c);
|
||||
void fillPolygon(const std::vector<std::pair<int,int>>& pts, uint32_t c);
|
||||
void drawText(Display* dpy, int x, int y, const std::string& t, uint32_t c, int scale = 1);
|
||||
void blit(const RawPicture& src, int dx, int dy);
|
||||
bool load(const std::string& path);
|
||||
void present(Display* dpy, Window win, GC gc, int dx, int dy) const;
|
||||
|
||||
private:
|
||||
int w = 0, h = 0, bits = 32;
|
||||
std::vector<uint8_t> buf;
|
||||
int bpp() const { return bits / 8; }
|
||||
};
|
||||
|
||||
// ================= Widgets =================
|
||||
enum class WidgetType {
|
||||
BUTTON, LABEL, INPUT, CHECKBOX, RADIO, SLIDER, PROGRESS,
|
||||
LISTBOX, DROPDOWN, TREE_VIEW, TREE, SCROLL, RAW_PIC, SPRITE_BTN
|
||||
};
|
||||
|
||||
struct WidgetData {
|
||||
WidgetType type = WidgetType::BUTTON;
|
||||
std::string text;
|
||||
std::string id;
|
||||
unsigned long bg_color = 0x1A1A1A;
|
||||
unsigned long fg_color = 0xFFFFFF;
|
||||
unsigned long frame_color = 0x00FF00;
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int width = 100;
|
||||
int height = 30;
|
||||
bool checked = false;
|
||||
float value = 0.0f;
|
||||
float target_value = 0.0f;
|
||||
int selected_index = -1;
|
||||
int scroll = 0;
|
||||
bool expanded = false;
|
||||
bool indeterminate = false;
|
||||
std::vector<std::string> items;
|
||||
std::shared_ptr<RawPicture> picture;
|
||||
callback_void on_change;
|
||||
callback_page on_page_click;
|
||||
callback_window on_window_click;
|
||||
};
|
||||
|
||||
using OverlayList = std::vector<const Widget*>;
|
||||
|
||||
class combat
|
||||
{
|
||||
friend struct Widget;
|
||||
protected:
|
||||
std::vector<std::unique_ptr<Widget>> widgets;
|
||||
Widget& add_widget(WidgetData d);
|
||||
public:
|
||||
virtual ~combat() = default;
|
||||
|
||||
Widget& Button(WidgetData a);
|
||||
Widget& Label(WidgetData a);
|
||||
Widget& Input(WidgetData a);
|
||||
Widget& Checkbox(WidgetData a);
|
||||
Widget& Radiobutton(WidgetData a);
|
||||
Widget& Slider(WidgetData a);
|
||||
Widget& Progressbar(WidgetData a);
|
||||
Widget& Listbox(WidgetData a);
|
||||
Widget& Dropdown(WidgetData a);
|
||||
Widget& TreeView(WidgetData a);
|
||||
Widget& TreeNode(WidgetData a);
|
||||
Widget& ScrollView(WidgetData a);
|
||||
Widget& RawPictureWidget(WidgetData a);
|
||||
Widget& SpriteButton(WidgetData a);
|
||||
|
||||
void visit(const std::function<void(Widget&)>& fn);
|
||||
Widget* find(const std::string& id);
|
||||
Widget* find_at(int mx, int my);
|
||||
|
||||
void tick(float dt);
|
||||
void render_all(Display*, Window, GC, int ox, int oy, const XRectangle& clip, OverlayList* ov) const;
|
||||
int process_press(int mx, int my, window& w, page& p, int ox, int oy);
|
||||
bool process_wheel(int delta, int mx, int my, int ox, int oy);
|
||||
};
|
||||
|
||||
struct Widget : combat
|
||||
{
|
||||
WidgetData data;
|
||||
float anim = 0;
|
||||
bool open_target = false;
|
||||
float phase = 0;
|
||||
int cap_ox = 0, cap_oy = 0;
|
||||
bool thumb_drag = false;
|
||||
int drag_my = 0, drag_scroll = 0;
|
||||
|
||||
void setValue(float v) { data.target_value = v; if (data.type != WidgetType::PROGRESS) data.value = v; }
|
||||
float getValue() const { return data.value; }
|
||||
|
||||
void tick(float dt);
|
||||
void draw(Display*, Window, GC, int ox, int oy, const XRectangle& clip, OverlayList* ov) const;
|
||||
void draw_popup(Display*, Window, GC, const XRectangle& clip) const;
|
||||
int handle_press(int mx, int my, window& w, page& p, int ox, int oy);
|
||||
void handle_drag(int mx, int my);
|
||||
void handle_release();
|
||||
bool handle_wheel(int delta, int mx, int my, int ox, int oy);
|
||||
bool contains(int mx, int my, int ox, int oy) const;
|
||||
int content_height() const;
|
||||
};
|
||||
|
||||
class page : public combat {
|
||||
public:
|
||||
bool active = false;
|
||||
int x = 0, y = 0;
|
||||
int width = 1200, height = 600;
|
||||
};
|
||||
|
||||
class window
|
||||
{
|
||||
private:
|
||||
Display* X11display = nullptr;
|
||||
Window X11window = 0;
|
||||
GC gc = nullptr;
|
||||
|
||||
std::vector<WidgetData> tab_buttons;
|
||||
std::unordered_map<std::string, std::unique_ptr<page>> pages;
|
||||
std::string current_page_name;
|
||||
|
||||
Widget* captured = nullptr;
|
||||
Widget* focused_input = nullptr;
|
||||
|
||||
void draw_tabs() const;
|
||||
void draw_active_page() const;
|
||||
void handle_event(const XEvent& ev);
|
||||
|
||||
public:
|
||||
explicit window(std::string title);
|
||||
~window();
|
||||
window(const window&) = delete;
|
||||
window& operator=(const window&) = delete;
|
||||
|
||||
Display* display() const { return X11display; }
|
||||
void update();
|
||||
|
||||
void addTAB_BUTTON(WidgetData args);
|
||||
void addPAGE(std::string name);
|
||||
void configur_page(std::string name, callback_page fn, bool active = false);
|
||||
void openPAGE(std::string name);
|
||||
Widget* find(const std::string& id);
|
||||
void set_focus(Widget* w) { focused_input = w; }
|
||||
};
|
||||
}
|
||||
#endif
|
||||
Loading…
x
Reference in New Issue
Block a user