The old DockWidget let panels dock to a side, resize, undock and be dragged to another side with a preview. The rewrite had replaced it with a SplitView, so panels were fixed in place. This brings docking back as a small tool-window system modeled on CLion. DockArea surrounds a center widget with stripes that list its DockPanels; clicking a stripe button toggles that panel. Each side (left, right, bottom) shows one panel at a time. A panel has three view modes, switched from its header: - docked (pinned): takes its zone and pushes the center aside; - undocked (unpinned): overlays the center and hides when the user activates anything outside it; - float: a window inside the area, moved by its header and resized from its corner grip. Docked panels resize by dragging their inner edge, clamped so the center stays visible. Dragging a header detaches the panel; a translucent preview marks the side it will land on when the pointer is over that edge, and releasing there docks it and moves its stripe button. Releasing elsewhere floats it. Unpinned auto-hide needs to know when activity leaves a subtree, so the Ui now tracks the last pressed widget. Its ancestors get containsActiveWidget() and onActiveWithinChanged(), reusing the same path diff as hover. Sketch3D now docks Tools (left) and Brush (right) around the viewport, with the View menu toggling them. 3DEditor docks Navigation (left) and Render (right). The gallery gains a Docking tab. 25 DockingTests and a gallery session cover stripes, one panel per side, docked layout, edge resize and its limit, unpin and auto-hide, stripe toggling of unpinned panels, drag with preview and drop on each side, floating move and resize, and panel lifetime.
169 lines
4.9 KiB
C++
169 lines
4.9 KiB
C++
#include "SketchGUI.hpp"
|
|
|
|
#include "BoxLayout.hpp"
|
|
#include "Event.hpp"
|
|
#include "StackLayout.hpp"
|
|
#include "Ui.hpp"
|
|
|
|
using namespace tp;
|
|
using namespace tp::ui;
|
|
|
|
namespace {
|
|
constexpr halnf kViewportRounding = 12;
|
|
|
|
PencilBrush* pencilOf(Project* project) {
|
|
if (!project) return nullptr;
|
|
return dynamic_cast<PencilBrush*>(project->mBrushes.get("pencil"));
|
|
}
|
|
}
|
|
|
|
SketchViewport::SketchViewport(Canvas& canvas, Vec2F renderResolution) :
|
|
mCanvas(canvas),
|
|
mRenderer(renderResolution),
|
|
mImage(canvas.createImageFromTextId(mRenderer.getBuff()->texId(), mRenderer.getBuff()->getSize())) {
|
|
setDebugName("sketch viewport");
|
|
setSizing(AxisSizing::fill(), AxisSizing::fill());
|
|
}
|
|
|
|
SketchViewport::~SketchViewport() { mCanvas.deleteImageHandle(mImage); }
|
|
|
|
void SketchViewport::onEvent(Event& event) {
|
|
if (!mProject) return;
|
|
|
|
switch (event.type()) {
|
|
case Event::Type::PointerDown:
|
|
if (event.button() != PointerButton::Primary) return;
|
|
mDrawing = true;
|
|
mPreviousPointer = event.position();
|
|
ui()->capturePointer(*this);
|
|
break;
|
|
|
|
case Event::Type::PointerUp:
|
|
if (!mDrawing) return;
|
|
mDrawing = false;
|
|
ui()->releasePointer(*this);
|
|
mProject->sample(0, size().y / size().x, normalized(event.position()));
|
|
break;
|
|
|
|
case Event::Type::PointerMove: applyAction(event); break;
|
|
|
|
default: return;
|
|
}
|
|
|
|
event.accept();
|
|
requestPaint();
|
|
}
|
|
|
|
void SketchViewport::paint(Painter& painter) {
|
|
if (!mProject) return;
|
|
|
|
mProject->mCamera.setRatio(size().y / size().x);
|
|
mRenderer.resize(size());
|
|
mRenderer.renderToTexture(mProject, size());
|
|
mCanvas.updateTextureID(mImage, mRenderer.getBuff()->texId());
|
|
painter.image(localRect(), { mImage.id }, kViewportRounding);
|
|
}
|
|
|
|
Vec2F SketchViewport::normalized(Vec2F local) const { return ((local / size()) - 0.5f) * 2.0f; }
|
|
|
|
void SketchViewport::applyAction(const Event& event) {
|
|
const Vec2F pointer = event.position();
|
|
const halnf ratio = size().y / size().x;
|
|
|
|
if (!mDrawing) {
|
|
mProject->sample(0, ratio, normalized(pointer));
|
|
mPreviousPointer = pointer;
|
|
return;
|
|
}
|
|
|
|
const Modifiers& modifiers = event.modifiers();
|
|
Camera& camera = mProject->mCamera;
|
|
const Vec2F current = normalized(pointer);
|
|
const Vec2F previous = normalized(mPreviousPointer);
|
|
|
|
if (modifiers.shift) camera.move(current, previous);
|
|
else if (modifiers.alt) camera.rotate(-(current - previous).x * halnf(PI), -(current - previous).y * halnf(PI));
|
|
else if (modifiers.control) camera.zoom(mPreviousPointer.y != 0 ? pointer.y / mPreviousPointer.y : 1.0f);
|
|
else mProject->sample(event.pressure() > 0 ? event.pressure() : 1.0f, ratio, current);
|
|
|
|
mPreviousPointer = pointer;
|
|
}
|
|
|
|
SketchGUI::SketchGUI(Canvas& canvas, Vec2F renderResolution) :
|
|
Panel(Surface::Window),
|
|
mViewport(canvas, renderResolution) {
|
|
setDebugName("sketch gui");
|
|
setSizing(AxisSizing::fill(), AxisSizing::fill());
|
|
emplaceLayout<StackLayout>();
|
|
|
|
mViewportArea.setSizing(AxisSizing::fill(), AxisSizing::fill());
|
|
mViewportArea.emplaceLayout<FreeLayout>().setPadding(Insets::uniform(6));
|
|
mViewportArea.addChild(mViewport);
|
|
|
|
buildToolbar();
|
|
buildToolsPanel();
|
|
buildBrushPanel();
|
|
|
|
mDock.setCenter(&mViewportArea);
|
|
mDock.addPanel(mToolsPanel);
|
|
mDock.addPanel(mBrushPanel);
|
|
mDock.openPanel(mBrushPanel);
|
|
addChild(mDock);
|
|
}
|
|
|
|
void SketchGUI::setProject(Project* project) {
|
|
mProject = project;
|
|
mViewport.setProject(project);
|
|
if (mProject) mProject->mBackgroundColor = { 0.13f, 0.13f, 0.13f, 0.8f };
|
|
}
|
|
|
|
void SketchGUI::buildToolbar() {
|
|
mToolbar.setSizing(AxisSizing::fit(), AxisSizing::fit());
|
|
mToolbar.emplaceLayout<RowLayout>(4).setPadding(Insets::uniform(4));
|
|
|
|
for (DockPanel* panel : { &mToolsPanel, &mBrushPanel }) {
|
|
mViewMenu.addToggle(
|
|
panel->title(), [panel]() { return panel->isOpen(); }, [this, panel]() { mDock.togglePanel(*panel); }
|
|
);
|
|
}
|
|
mViewButton.setPopupContent(&mViewMenu);
|
|
mViewButton.setOpenOnHover(true);
|
|
mToolbar.addChild(mViewButton);
|
|
|
|
mToolbar.requestPosition({ 12, 12 });
|
|
mViewportArea.addChild(mToolbar);
|
|
}
|
|
|
|
void SketchGUI::buildToolsPanel() {
|
|
mToolsContent.emplaceLayout<ColumnLayout>(6).setCrossAlign(CrossAlign::Start);
|
|
|
|
mPencil.setOnActivate([this]() {
|
|
if (mProject) mProject->setPencil();
|
|
});
|
|
mEraser.setOnActivate([this]() {
|
|
if (mProject) mProject->setEraser();
|
|
});
|
|
|
|
mToolsContent.addChild(mPencil);
|
|
mToolsContent.addChild(mEraser);
|
|
mToolsPanel.setContent(&mToolsContent);
|
|
mToolsPanel.setDockedLength(180);
|
|
}
|
|
|
|
void SketchGUI::buildBrushPanel() {
|
|
mBrushContent.emplaceLayout<ColumnLayout>(8);
|
|
|
|
mColorWheel.setSizing(AxisSizing::fill(), AxisSizing::fixed(220));
|
|
mColorWheel.setColor(RGBA(1, 1, 1, 1));
|
|
mColorWheel.setOnChanged([this](const RGBA& color) {
|
|
if (PencilBrush* pencil = pencilOf(mProject)) pencil->mCol = color;
|
|
});
|
|
mBrushSize.setOnChanged([this](halnf size) {
|
|
if (PencilBrush* pencil = pencilOf(mProject)) pencil->mSize = size;
|
|
});
|
|
|
|
mBrushContent.addChild(mColorWheel);
|
|
mBrushContent.addChild(mBrushSize);
|
|
mBrushPanel.setContent(&mBrushContent);
|
|
mBrushPanel.setDockedLength(280);
|
|
}
|