The colour popup now has five pages like the reference panel: the wheel, a saturation/value square with a hue strip, and RGB, HSV and HSL sliders whose tracks show the colour they would pick. A footer holds the old/new colour swatch (tapping the old half restores it), a hex field and a Copy button. Grey colours keep their hue across pages. On tablets the menu gains a Color submenu with the colour commands and lightness presets, and Settings lists the Color menu. Painter gains a linear gradient fill, Slider an optional gradient track and Widgets a ColorSquare.
610 lines
No EOL
18 KiB
C++
610 lines
No EOL
18 KiB
C++
#include "Window.hpp"
|
|
#include "Profiler.hpp"
|
|
#include "WindowContext.hpp"
|
|
|
|
#include "Graphics.hpp"
|
|
#include "RenderTargetRegistry.hpp"
|
|
|
|
#include <nanovg.h>
|
|
#include <nanovg_vk_api.h>
|
|
|
|
namespace tp {
|
|
class Canvas::Context {
|
|
public:
|
|
NVGcontext* vg{};
|
|
NVGcontext* chrome{};
|
|
Window* window{};
|
|
};
|
|
}
|
|
|
|
using namespace tp;
|
|
|
|
namespace {
|
|
const NVGcolor kWindowBaseColor = nvgRGBAf(0.118f, 0.122f, 0.133f, 1.0f);
|
|
const NVGcolor kWindowOutlineColor = nvgRGBAf(1.0f, 1.0f, 1.0f, 0.07f);
|
|
const NVGcolor kFocusedShadowColor = nvgRGBAf(0, 0, 0, 0.42f);
|
|
const NVGcolor kUnfocusedShadowColor = nvgRGBAf(0, 0, 0, 0.24f);
|
|
constexpr halnf kShadowFeatherFraction = 0.9f;
|
|
constexpr halnf kShadowOffsetY = 3;
|
|
}
|
|
|
|
Canvas::Canvas(Window* window) {
|
|
mContext = new Context();
|
|
mContext->window = window;
|
|
|
|
VulkanContext* vulkan = window->getContext()->vulkan;
|
|
|
|
VKNVGCreateInfo createInfo{};
|
|
createInfo.gpu = vulkan->physicalDevice();
|
|
createInfo.device = vulkan->device();
|
|
createInfo.renderpass = vulkan->renderPass();
|
|
createInfo.cmdBuffer = vulkan->commandBufferArray();
|
|
createInfo.currentFrame = vulkan->currentFrameSlot();
|
|
createInfo.swapchainImageCount = vulkan->framesInFlight();
|
|
|
|
mContext->vg = nvgCreateVkApi(createInfo, NVG_ANTIALIAS | NVG_STENCIL_STROKES, vulkan->graphicsQueue());
|
|
mContext->chrome = nvgCreateVkApi(createInfo, NVG_ANTIALIAS, vulkan->graphicsQueue());
|
|
|
|
if (nvgCreateFont(mContext->vg, "default", "rsc/Font.ttf") == -1) {
|
|
ASSERT(!"Cant create NVG font")
|
|
}
|
|
}
|
|
|
|
Canvas::~Canvas() {
|
|
mContext->window->getContext()->vulkan->waitIdle();
|
|
nvgDeleteVkApi(mContext->chrome);
|
|
nvgDeleteVkApi(mContext->vg);
|
|
delete mContext;
|
|
}
|
|
|
|
void Canvas::rect(RectF rec, const RGBA& col, halnf round) {
|
|
rec.pos += mOrigin;
|
|
|
|
nvgBeginPath(mContext->vg);
|
|
|
|
if (round == 0) {
|
|
nvgRect(mContext->vg, rec.x, rec.y, rec.z, rec.w);
|
|
} else {
|
|
nvgRoundedRect(mContext->vg, rec.x, rec.y, rec.z, rec.w, round);
|
|
}
|
|
|
|
nvgFillColor(mContext->vg, { col.r, col.g, col.b, col.a });
|
|
nvgFill(mContext->vg);
|
|
}
|
|
|
|
void Canvas::gradientRect(RectF rec, const RGBA& from, const RGBA& to, bool horizontal, halnf round) {
|
|
rec.pos += mOrigin;
|
|
auto vg = mContext->vg;
|
|
const halnf endX = horizontal ? rec.x + rec.z : rec.x;
|
|
const halnf endY = horizontal ? rec.y : rec.y + rec.w;
|
|
const NVGpaint paint =
|
|
nvgLinearGradient(vg, rec.x, rec.y, endX, endY, { from.r, from.g, from.b, from.a }, { to.r, to.g, to.b, to.a });
|
|
nvgBeginPath(vg);
|
|
if (round == 0) nvgRect(vg, rec.x, rec.y, rec.z, rec.w);
|
|
else nvgRoundedRect(vg, rec.x, rec.y, rec.z, rec.w, round);
|
|
nvgFillPaint(vg, paint);
|
|
nvgFill(vg);
|
|
}
|
|
|
|
void Canvas::debugCross(RectF rec, const RGBA& col) {
|
|
nvgBeginPath(mContext->vg);
|
|
|
|
nvgMoveTo(mContext->vg, rec.p1().x, rec.p1().y);
|
|
nvgLineTo(mContext->vg, rec.p3().x, rec.p3().y);
|
|
|
|
nvgMoveTo(mContext->vg, rec.p2().x, rec.p2().y);
|
|
nvgLineTo(mContext->vg, rec.p4().x, rec.p4().y);
|
|
|
|
nvgStrokeWidth(mContext->vg, 2);
|
|
nvgStrokeColor(mContext->vg, { col.r, col.g, col.b, col.a });
|
|
nvgStroke(mContext->vg);
|
|
// nvgFill(mContext->vg);
|
|
}
|
|
|
|
void Canvas::frame(RectF rec, const RGBA& col, halnf round) {
|
|
nvgBeginPath(mContext->vg);
|
|
|
|
nvgMoveTo(mContext->vg, rec.p1().x, rec.p1().y);
|
|
nvgLineTo(mContext->vg, rec.p2().x, rec.p2().y);
|
|
nvgLineTo(mContext->vg, rec.p3().x, rec.p3().y);
|
|
nvgLineTo(mContext->vg, rec.p4().x, rec.p4().y);
|
|
nvgLineTo(mContext->vg, rec.p1().x, rec.p1().y);
|
|
|
|
nvgStrokeWidth(mContext->vg, 2);
|
|
nvgStrokeColor(mContext->vg, { col.r, col.g, col.b, col.a });
|
|
nvgStroke(mContext->vg);
|
|
// nvgFill(mContext->vg);
|
|
}
|
|
|
|
void Canvas::outline(RectF rec, const RGBA& col, halnf round, halnf width) {
|
|
rec.pos += mOrigin;
|
|
|
|
const halnf inset = width / 2;
|
|
nvgBeginPath(mContext->vg);
|
|
nvgRoundedRect(
|
|
mContext->vg, rec.x + inset, rec.y + inset, rec.z - width, rec.w - width, round > inset ? round - inset : 0
|
|
);
|
|
nvgStrokeWidth(mContext->vg, width);
|
|
nvgStrokeColor(mContext->vg, { col.r, col.g, col.b, col.a });
|
|
nvgStroke(mContext->vg);
|
|
}
|
|
|
|
void Canvas::circleOutline(Vec2F pos, halnf size, const RGBA& col, halnf width) {
|
|
pos += mOrigin;
|
|
|
|
nvgBeginPath(mContext->vg);
|
|
nvgCircle(mContext->vg, pos.x, pos.y, size - width / 2);
|
|
nvgStrokeWidth(mContext->vg, width);
|
|
nvgStrokeColor(mContext->vg, { col.r, col.g, col.b, col.a });
|
|
nvgStroke(mContext->vg);
|
|
}
|
|
|
|
void Canvas::polyline(const Vec2F* points, int count, const RGBA& col, halnf width) {
|
|
if (count < 2) return;
|
|
|
|
nvgBeginPath(mContext->vg);
|
|
nvgMoveTo(mContext->vg, points[0].x + mOrigin.x, points[0].y + mOrigin.y);
|
|
for (int index = 1; index < count; index++) {
|
|
nvgLineTo(mContext->vg, points[index].x + mOrigin.x, points[index].y + mOrigin.y);
|
|
}
|
|
nvgLineCap(mContext->vg, NVG_ROUND);
|
|
nvgLineJoin(mContext->vg, NVG_ROUND);
|
|
nvgStrokeWidth(mContext->vg, width);
|
|
nvgStrokeColor(mContext->vg, { col.r, col.g, col.b, col.a });
|
|
nvgStroke(mContext->vg);
|
|
}
|
|
|
|
void Canvas::polygon(const Vec2F* points, int count, const RGBA& col) {
|
|
if (count < 3) return;
|
|
|
|
nvgBeginPath(mContext->vg);
|
|
nvgMoveTo(mContext->vg, points[0].x + mOrigin.x, points[0].y + mOrigin.y);
|
|
for (int index = 1; index < count; index++) {
|
|
nvgLineTo(mContext->vg, points[index].x + mOrigin.x, points[index].y + mOrigin.y);
|
|
}
|
|
nvgClosePath(mContext->vg);
|
|
nvgFillColor(mContext->vg, { col.r, col.g, col.b, col.a });
|
|
nvgFill(mContext->vg);
|
|
}
|
|
|
|
void Canvas::line(Vec2F start, Vec2F end, const RGBA& col, halnf thickness) {
|
|
start += mOrigin;
|
|
end += mOrigin;
|
|
|
|
nvgBeginPath(mContext->vg);
|
|
nvgFillColor(mContext->vg, { col.r, col.g, col.b, col.a });
|
|
nvgMoveTo(mContext->vg, start.x, start.y);
|
|
nvgLineTo(mContext->vg, end.x, end.y);
|
|
nvgStrokeWidth(mContext->vg, thickness);
|
|
nvgStrokeColor(mContext->vg, { col.r, col.g, col.b, col.a });
|
|
nvgStroke(mContext->vg);
|
|
}
|
|
|
|
void Canvas::circle(Vec2F pos, halnf size, const RGBA& col) {
|
|
pos += mOrigin;
|
|
|
|
nvgBeginPath(mContext->vg);
|
|
nvgCircle(mContext->vg, pos.x, pos.y, size);
|
|
|
|
nvgFillColor(mContext->vg, { col.r, col.g, col.b, col.a });
|
|
nvgFill(mContext->vg);
|
|
}
|
|
|
|
void Canvas::setOrigin(const Vec2F& origin) { mOrigin = origin; }
|
|
|
|
void Canvas::pushClamp(const RectF& rec) {
|
|
RectF intersection = rec;
|
|
if (mScissors.size()) {
|
|
mScissors.last().calcIntersection(rec, intersection);
|
|
}
|
|
nvgScissor(mContext->vg, intersection.x, intersection.y, intersection.z, intersection.w);
|
|
mScissors.append(intersection);
|
|
}
|
|
|
|
void Canvas::popClamp() {
|
|
DEBUG_ASSERT(mScissors.size());
|
|
mIsClamping = false;
|
|
mScissors.pop();
|
|
if (mScissors.size()) {
|
|
const auto& rec = mScissors.last();
|
|
nvgScissor(mContext->vg, rec.x, rec.y, rec.z, rec.w);
|
|
} else {
|
|
nvgResetScissor(mContext->vg);
|
|
}
|
|
}
|
|
|
|
const RectF& Canvas::getClampedArea() const { return mScissors.last(); }
|
|
|
|
void Canvas::text(const char* string, const RectF& aRec, halnf size, Align align, halnf marging, const RGBA& col) {
|
|
|
|
RectF rec = { aRec.x + marging, aRec.y + marging, aRec.z - marging * 2, aRec.w - marging * 2 };
|
|
rec.pos += mOrigin;
|
|
|
|
pushClamp(rec);
|
|
|
|
nvgFontSize(mContext->vg, size);
|
|
nvgFontFace(mContext->vg, "default");
|
|
nvgFillColor(mContext->vg, { col.r, col.g, col.b, col.a });
|
|
|
|
float centerX = rec.x;
|
|
float centerY = rec.y;
|
|
int alignNVG = 0;
|
|
|
|
if (((int1*) &align)[1] == 0x00) { // center x
|
|
alignNVG |= NVG_ALIGN_CENTER;
|
|
centerX += rec.z * 0.5f;
|
|
} else if (((int1*) &align)[1] == 0x01) { // left x
|
|
alignNVG |= NVG_ALIGN_LEFT;
|
|
} else if (((int1*) &align)[1] == 0x02) { // right x
|
|
alignNVG |= NVG_ALIGN_RIGHT;
|
|
centerX += rec.z;
|
|
}
|
|
|
|
if (((int1*) &align)[0] == 0x00) { // center y
|
|
alignNVG |= NVG_ALIGN_MIDDLE;
|
|
centerY += rec.w * 0.5f;
|
|
} else if (((int1*) &align)[0] == 0x01) { // top y
|
|
alignNVG |= NVG_ALIGN_TOP;
|
|
centerY += rec.w;
|
|
} else if (((int1*) &align)[0] == 0x02) { // bottom y
|
|
alignNVG |= NVG_ALIGN_BOTTOM;
|
|
}
|
|
|
|
nvgTextAlign(mContext->vg, alignNVG);
|
|
|
|
nvgText(mContext->vg, centerX, centerY, string, nullptr);
|
|
|
|
popClamp();
|
|
}
|
|
|
|
void Canvas::rotatedText(const char* string, Vec2F center, halnf size, halnf angle, const RGBA& col) {
|
|
center += mOrigin;
|
|
|
|
nvgSave(mContext->vg);
|
|
nvgTranslate(mContext->vg, center.x, center.y);
|
|
nvgRotate(mContext->vg, angle);
|
|
nvgFontSize(mContext->vg, size);
|
|
nvgFontFace(mContext->vg, "default");
|
|
nvgTextAlign(mContext->vg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE);
|
|
nvgFillColor(mContext->vg, { col.r, col.g, col.b, col.a });
|
|
nvgText(mContext->vg, 0, 0, string, nullptr);
|
|
nvgRestore(mContext->vg);
|
|
}
|
|
|
|
halnf Canvas::textWidth(const char* string, halnf size) {
|
|
nvgFontSize(mContext->vg, size);
|
|
nvgFontFace(mContext->vg, "default");
|
|
float bounds[4] = { 0, 0, 0, 0 };
|
|
nvgTextBounds(mContext->vg, 0, 0, string, nullptr, bounds);
|
|
return bounds[2] - bounds[0];
|
|
}
|
|
|
|
void Canvas::drawImage(RectF rec, ImageHandle* image, halnf angle, halnf alpha, halnf rounding) {
|
|
rec.pos += mOrigin;
|
|
|
|
auto imgPaint = nvgImagePattern(mContext->vg, rec.x, rec.y, rec.z, rec.w, angle, image->id, alpha);
|
|
nvgBeginPath(mContext->vg);
|
|
nvgRoundedRect(mContext->vg, rec.x, rec.y, rec.z, rec.w, rounding);
|
|
nvgFillPaint(mContext->vg, imgPaint);
|
|
nvgFill(mContext->vg);
|
|
}
|
|
|
|
Canvas::ImageHandle Canvas::createImageFromTextId(ualni id, Vec2F size) {
|
|
RenderTargetEntry entry;
|
|
if (!RenderTargetRegistry::resolve((uint32_t) id, entry)) {
|
|
return { 0 };
|
|
}
|
|
int image = nvgCreateImageFromVkImageApi(mContext->vg, entry.view, entry.sampler, entry.width, entry.height);
|
|
return { (ualni) image };
|
|
}
|
|
|
|
void Canvas::updateTextureID(ImageHandle handle, ualni id) {
|
|
RenderTargetEntry entry;
|
|
if (!RenderTargetRegistry::resolve((uint32_t) id, entry)) return;
|
|
nvgUpdateVkImageApi(mContext->vg, (int) handle.id, entry.view, entry.sampler, entry.width, entry.height);
|
|
}
|
|
|
|
Canvas::ImageHandle Canvas::createImageFromFile(const char* path) {
|
|
int image = nvgCreateImage(mContext->vg, path, 0);
|
|
return { (ualni) image };
|
|
}
|
|
|
|
Canvas::ImageHandle Canvas::createImageFromMemoryRGBA(int width, int height, const unsigned char* rgba) {
|
|
int image = nvgCreateImageRGBA(mContext->vg, width, height, 0, rgba);
|
|
return { (ualni) image };
|
|
}
|
|
|
|
void Canvas::updateImageRGBA(ImageHandle image, const unsigned char* rgba) {
|
|
if (image.id) nvgUpdateImage(mContext->vg, (int) image.id, rgba);
|
|
}
|
|
|
|
void Canvas::deleteImageHandle(ImageHandle image) {
|
|
if (image.id) {
|
|
nvgDeleteImage(mContext->vg, image.id);
|
|
}
|
|
}
|
|
|
|
void Canvas::drawBegin() {
|
|
const WindowFrame& frame = mContext->window->frame();
|
|
const Vec2F surfaceSize = frame.surfaceSize();
|
|
const Vec2F framebufferSize = mContext->window->getFramebufferSize();
|
|
const halnf devicePixelRatio = surfaceSize.x > 0 ? framebufferSize.x / surfaceSize.x : 1.0f;
|
|
|
|
nvgBeginFrame(mContext->vg, surfaceSize.x, surfaceSize.y, devicePixelRatio);
|
|
nvgTranslate(mContext->vg, frame.margin(), frame.margin());
|
|
fillWindowBase(frame);
|
|
}
|
|
|
|
void Canvas::drawEnd() {
|
|
TP_PROFILE_SCOPE("frame.ui_flush");
|
|
nvgEndFrame(mContext->vg);
|
|
}
|
|
|
|
void Canvas::drawWindowChrome() {
|
|
const WindowFrame& frame = mContext->window->frame();
|
|
if (!frame.hasShadow()) return;
|
|
|
|
const Vec2F surfaceSize = frame.surfaceSize();
|
|
const Vec2F framebufferSize = mContext->window->getFramebufferSize();
|
|
const halnf devicePixelRatio = surfaceSize.x > 0 ? framebufferSize.x / surfaceSize.x : 1.0f;
|
|
NVGcontext* chrome = mContext->chrome;
|
|
|
|
nvgBeginFrame(chrome, surfaceSize.x, surfaceSize.y, devicePixelRatio);
|
|
cutRoundedCorners(chrome, frame);
|
|
paintOutline(chrome, frame);
|
|
paintShadow(chrome, frame, mContext->window->isFocused());
|
|
nvgEndFrame(chrome);
|
|
}
|
|
|
|
void Canvas::fillWindowBase(const WindowFrame& frame) {
|
|
const Vec2F size = frame.contentSize();
|
|
|
|
nvgBeginPath(mContext->vg);
|
|
nvgRect(mContext->vg, 0, 0, size.x, size.y);
|
|
nvgFillColor(mContext->vg, kWindowBaseColor);
|
|
nvgFill(mContext->vg);
|
|
}
|
|
|
|
void Canvas::cutRoundedCorners(NVGcontext* vg, const WindowFrame& frame) {
|
|
const RectF content = frame.contentRect();
|
|
|
|
nvgSave(vg);
|
|
nvgGlobalCompositeOperation(vg, NVG_DESTINATION_OUT);
|
|
nvgBeginPath(vg);
|
|
nvgRect(vg, content.x, content.y, content.z, content.w);
|
|
nvgRoundedRect(vg, content.x, content.y, content.z, content.w, frame.cornerRadius());
|
|
nvgPathWinding(vg, NVG_HOLE);
|
|
nvgFillColor(vg, nvgRGBAf(0, 0, 0, 1));
|
|
nvgFill(vg);
|
|
nvgRestore(vg);
|
|
}
|
|
|
|
void Canvas::paintOutline(NVGcontext* vg, const WindowFrame& frame) {
|
|
const RectF content = frame.contentRect();
|
|
const halnf inset = 0.5f;
|
|
|
|
nvgBeginPath(vg);
|
|
nvgRoundedRect(
|
|
vg, content.x + inset, content.y + inset, content.z - inset * 2, content.w - inset * 2, frame.cornerRadius() - inset
|
|
);
|
|
nvgStrokeWidth(vg, 1);
|
|
nvgStrokeColor(vg, kWindowOutlineColor);
|
|
nvgStroke(vg);
|
|
}
|
|
|
|
void Canvas::paintShadow(NVGcontext* vg, const WindowFrame& frame, bool focused) {
|
|
const RectF content = frame.contentRect();
|
|
const Vec2F surface = frame.surfaceSize();
|
|
const halnf feather = frame.margin() * kShadowFeatherFraction;
|
|
const NVGcolor shadow = focused ? kFocusedShadowColor : kUnfocusedShadowColor;
|
|
|
|
const NVGpaint paint = nvgBoxGradient(
|
|
vg,
|
|
content.x,
|
|
content.y + kShadowOffsetY,
|
|
content.z,
|
|
content.w,
|
|
frame.cornerRadius(),
|
|
feather,
|
|
shadow,
|
|
nvgRGBAf(0, 0, 0, 0)
|
|
);
|
|
|
|
nvgSave(vg);
|
|
nvgGlobalCompositeOperation(vg, NVG_DESTINATION_OVER);
|
|
nvgBeginPath(vg);
|
|
nvgRect(vg, 0, 0, surface.x, surface.y);
|
|
nvgFillPaint(vg, paint);
|
|
nvgFill(vg);
|
|
nvgRestore(vg);
|
|
}
|
|
|
|
void Canvas::colorWheel(const RectF& rec, const ColorWheel& colorWheel) {
|
|
auto NVG = mContext->vg;
|
|
const auto sizeInner = 5.f;
|
|
const auto sizeOuter = 7.f;
|
|
|
|
float const x = rec.x + mOrigin.x;
|
|
float const y = rec.y + mOrigin.y;
|
|
float const w = rec.z;
|
|
float const h = rec.w;
|
|
|
|
const HSV& hsv = colorWheel.color;
|
|
float const hue = hsv.h / (NVG_PI * 2);
|
|
|
|
int i;
|
|
float r0, r1, ax, ay, bx, by, cx, cy, aeps, r;
|
|
NVGpaint paint;
|
|
|
|
nvgSave(mContext->vg);
|
|
|
|
cx = x + w * 0.5f;
|
|
cy = y + h * 0.5f;
|
|
r1 = (w < h ? w : h) * 0.5f - colorWheel.margin;
|
|
r0 = r1 - colorWheel.thickness;
|
|
|
|
aeps = 0.5f / r1; // half a pixel arc length in radians (2pi cancels out).
|
|
|
|
for (i = 0; i < 6; i++) {
|
|
float a0 = (float) i / 6.0f * NVG_PI * 2.0f - aeps;
|
|
float a1 = (float) (i + 1.0f) / 6.0f * NVG_PI * 2.0f + aeps;
|
|
nvgBeginPath(NVG);
|
|
nvgArc(NVG, cx, cy, r0, a0, a1, NVG_CW);
|
|
nvgArc(NVG, cx, cy, r1, a1, a0, NVG_CCW);
|
|
nvgClosePath(NVG);
|
|
ax = cx + cosf(a0) * (r0 + r1) * 0.5f;
|
|
ay = cy + sinf(a0) * (r0 + r1) * 0.5f;
|
|
bx = cx + cosf(a1) * (r0 + r1) * 0.5f;
|
|
by = cy + sinf(a1) * (r0 + r1) * 0.5f;
|
|
paint = nvgLinearGradient(
|
|
NVG, ax, ay, bx, by, nvgHSLA(a0 / (NVG_PI * 2), 1.0f, 0.55f, 255), nvgHSLA(a1 / (NVG_PI * 2), 1.0f, 0.55f, 255)
|
|
);
|
|
nvgFillPaint(NVG, paint);
|
|
nvgFill(NVG);
|
|
}
|
|
|
|
nvgBeginPath(NVG);
|
|
nvgCircle(NVG, cx, cy, r0 - 0.5f);
|
|
nvgCircle(NVG, cx, cy, r1 + 0.5f);
|
|
nvgStrokeColor(NVG, nvgRGBA(0, 0, 0, 64));
|
|
nvgStrokeWidth(NVG, 1.0f);
|
|
nvgStroke(NVG);
|
|
|
|
// Selector
|
|
nvgSave(NVG);
|
|
nvgTranslate(NVG, cx, cy);
|
|
nvgRotate(NVG, hue * NVG_PI * 2);
|
|
|
|
// Marker on
|
|
nvgStrokeWidth(NVG, 2.0f);
|
|
nvgBeginPath(NVG);
|
|
nvgRect(NVG, r0 - 1, -3, r1 - r0 + 2, 6);
|
|
nvgStrokeColor(NVG, nvgRGBA(255, 255, 255, 192));
|
|
nvgStroke(NVG);
|
|
|
|
paint = nvgBoxGradient(NVG, r0 - 3, -5, r1 - r0 + 6, 10, 2, 4, nvgRGBA(0, 0, 0, 128), nvgRGBA(0, 0, 0, 0));
|
|
nvgBeginPath(NVG);
|
|
nvgRect(NVG, r0 - 2 - 10, -4 - 10, r1 - r0 + 4 + 20, 8 + 20);
|
|
nvgRect(NVG, r0 - 2, -4, r1 - r0 + 4, 8);
|
|
nvgPathWinding(NVG, NVG_HOLE);
|
|
nvgFillPaint(NVG, paint);
|
|
nvgFill(NVG);
|
|
|
|
// Center triangle
|
|
r = r0 - 6;
|
|
ax = cosf(120.0f / 180.0f * NVG_PI) * r;
|
|
ay = sinf(120.0f / 180.0f * NVG_PI) * r;
|
|
bx = cosf(-120.0f / 180.0f * NVG_PI) * r;
|
|
by = sinf(-120.0f / 180.0f * NVG_PI) * r;
|
|
nvgBeginPath(NVG);
|
|
nvgMoveTo(NVG, r, 0);
|
|
nvgLineTo(NVG, ax, ay);
|
|
nvgLineTo(NVG, bx, by);
|
|
nvgClosePath(NVG);
|
|
paint = nvgLinearGradient(NVG, r, 0, ax, ay, nvgHSLA(hue, 1.0f, 0.5f, 255), nvgRGBA(255, 255, 255, 255));
|
|
nvgFillPaint(NVG, paint);
|
|
nvgFill(NVG);
|
|
paint = nvgLinearGradient(NVG, (r + ax) * 0.5f, (0 + ay) * 0.5f, bx, by, nvgRGBA(0, 0, 0, 0), nvgRGBA(0, 0, 0, 255));
|
|
nvgFillPaint(NVG, paint);
|
|
nvgFill(NVG);
|
|
nvgStrokeColor(NVG, nvgRGBA(0, 0, 0, 64));
|
|
nvgStroke(NVG);
|
|
|
|
// Select circle on triangle
|
|
float yt = hsv.v * hsv.s;
|
|
float xt = hsv.v - 0.5 * yt;
|
|
ay = sinf(120.0f / 180.0f * NVG_PI) * r * (-1.0f + xt * 2.0f);
|
|
ax = cosf(120.0f / 180.0f * NVG_PI) * r * (1.0f - yt * 3.f);
|
|
nvgStrokeWidth(NVG, 2.0f);
|
|
nvgBeginPath(NVG);
|
|
nvgCircle(NVG, ax, ay, sizeInner);
|
|
nvgStrokeColor(NVG, nvgRGBA(255, 255, 255, 192));
|
|
nvgStroke(NVG);
|
|
|
|
paint = nvgRadialGradient(NVG, ax, ay, 7, 9, nvgRGBA(0, 0, 0, 64), nvgRGBA(0, 0, 0, 0));
|
|
nvgBeginPath(NVG);
|
|
nvgRect(NVG, ax - 20, ay - 20, 40, 40);
|
|
nvgCircle(NVG, ax, ay, sizeOuter);
|
|
nvgPathWinding(NVG, NVG_HOLE);
|
|
nvgFillPaint(NVG, paint);
|
|
nvgFill(NVG);
|
|
|
|
nvgRestore(NVG);
|
|
|
|
nvgRestore(NVG);
|
|
}
|
|
|
|
void Canvas::ColorWheel::fromPoint(const tp::RectF& area, const tp::Vec2F& crs) {
|
|
auto wheel_rec = area;
|
|
wheel_rec.pos += margin;
|
|
wheel_rec.size -= margin * 2;
|
|
|
|
auto center = wheel_rec.pos + wheel_rec.size / 2.f;
|
|
auto edge = min(wheel_rec.size.x, wheel_rec.size.y);
|
|
wheel_rec.pos = center - edge / 2.f;
|
|
wheel_rec.size = edge;
|
|
|
|
HSV hsv = color;
|
|
|
|
auto pos = crs - center;
|
|
|
|
auto r = (crs - center).length();
|
|
if (r < edge / 2.f) {
|
|
if (r > edge / 2.f - thickness) {
|
|
auto angle = halnf(atan2(pos.y, pos.x));
|
|
angle = halnf(angle > 0 ? angle : PI * 2 + angle);
|
|
angle = clamp(angle, 0.f, halnf(PI * 2));
|
|
hsv.h = angle;
|
|
} else {
|
|
pos.y *= -1;
|
|
pos /= (edge / 2.f) - thickness - 10;
|
|
|
|
auto angle = PI * 2 - hsv.h - PI / 2;
|
|
if (angle < 0) {
|
|
angle += PI * 2;
|
|
}
|
|
|
|
auto sin = halnf(::sin(-angle));
|
|
auto cos = halnf(::cos(-angle));
|
|
auto sv_pos = Vec2F{ pos.x * cos - pos.y * sin, pos.x * sin + pos.y * cos };
|
|
|
|
auto sv_angle = halnf(atan2(sv_pos.x, sv_pos.y));
|
|
sv_angle = halnf(sv_angle > 0 ? sv_angle : PI * 2 + sv_angle);
|
|
sv_angle = clamp(sv_angle, 0.f, halnf(PI * 2));
|
|
|
|
auto p1 = Vec2F(0, 1);
|
|
auto p2 = Vec2F(::cos(PI / 6), -::sin(PI / 6));
|
|
auto p3 = Vec2F{ -p2.x, p2.y };
|
|
|
|
Vec2F intersection = sv_pos;
|
|
if (sv_angle < PI / 3 * 2) {
|
|
if (intersectLines2D({ 0, 0 }, sv_pos, p1, p2, &intersection)) {
|
|
sv_pos = intersection;
|
|
}
|
|
} else if (sv_angle < PI / 3 * 4) {
|
|
if (intersectLines2D({ 0, 0 }, sv_pos, p3, p2, &intersection)) {
|
|
sv_pos = intersection;
|
|
}
|
|
} else {
|
|
if (intersectLines2D({ 0, 0 }, sv_pos, p1, p3, &intersection)) {
|
|
sv_pos = intersection;
|
|
}
|
|
}
|
|
|
|
sv_pos.y = halnf((sv_pos.y + 1 / 2.f) * 2 / 3.f * 1.0);
|
|
sv_pos.x = halnf(sv_pos.x * 2 / sqrt(3) * 1.0);
|
|
sv_pos.x = (sv_pos.x + 1.f) / 2;
|
|
|
|
sv_pos.x = clamp(sv_pos.x, 0.f, 1.f);
|
|
sv_pos.y = clamp(sv_pos.y, 0.f, 1.f);
|
|
|
|
auto v = sv_pos.y * 0.5f + sv_pos.x;
|
|
auto s = sv_pos.y / v;
|
|
|
|
hsv.v = clamp(v, 0.001f, 0.999f);
|
|
hsv.s = clamp(s, 0.001f, 0.999f);
|
|
}
|
|
}
|
|
|
|
color = hsv;
|
|
} |