The Brush menu gains Reset all brushes, Smaller/Bigger brush, a Reset brush submenu (everything, parameters, stabilization, textures), Show Brush list (F4) and Show Brush options (F5), an Adjust submenu with flow, opacity and angle steps and the brush size and angle tools, and the opacity presets 10-100 %. Holding D, A or the flow and opacity tool keys turns a canvas drag into a live adjustment with a value label instead of a stroke; [ and ] resize by 20 %. The brush options popup has a Reset brush button. On tablets, holding the size button or choosing Adjust size/opacity/flow/angle in the quick menu arms one drag on the canvas, and the quick menu can reset the brush. Command ids follow the reference app.
174 lines
4.5 KiB
C++
174 lines
4.5 KiB
C++
#include "UiDriver.hpp"
|
|
|
|
#include "Slider.hpp"
|
|
|
|
#include <vector>
|
|
|
|
using namespace tp;
|
|
using namespace tp::ui;
|
|
using namespace tp::ui::testing;
|
|
|
|
UiDriver::UiDriver(Widget& root, Vec2F viewportSize) {
|
|
mUi.setTextMetrics(&mTextMetrics);
|
|
mUi.setViewport(makeRect({ 0, 0 }, viewportSize));
|
|
mUi.setRoot(&root);
|
|
mUi.updateLayout();
|
|
}
|
|
|
|
void UiDriver::finishLayoutAnimations() {
|
|
if (!mAutoSettle) return;
|
|
|
|
for (int pass = 0; pass < 8; pass++) {
|
|
mUi.updateLayout();
|
|
std::vector<Widget*> moving;
|
|
for (Widget* widget : mUi.animatingWidgets()) {
|
|
if (widget->isGeometryMoving() || widget->isTransitioning()) moving.push_back(widget);
|
|
}
|
|
if (moving.empty()) return;
|
|
for (Widget* widget : moving)
|
|
widget->finishGeometryAnimation();
|
|
}
|
|
mUi.updateLayout();
|
|
}
|
|
|
|
UiDriver::~UiDriver() {
|
|
mUi.closeAllPopups();
|
|
mUi.setRoot(nullptr);
|
|
}
|
|
|
|
void UiDriver::layout() {
|
|
mUi.updateLayout();
|
|
finishLayoutAnimations();
|
|
}
|
|
|
|
void UiDriver::resize(Vec2F viewportSize) {
|
|
mUi.setViewport(makeRect({ 0, 0 }, viewportSize));
|
|
finishLayoutAnimations();
|
|
}
|
|
|
|
void UiDriver::moveTo(Vec2F windowPoint) {
|
|
const Vec2F delta = windowPoint - mPointer;
|
|
mPointer = windowPoint;
|
|
mUi.dispatch(
|
|
Event::pointer(Event::Type::PointerMove, windowPoint, delta, PointerButton::Primary).withSample(mSample)
|
|
);
|
|
finishLayoutAnimations();
|
|
}
|
|
|
|
void UiDriver::moveTo(const Widget& widget) { moveTo(centerOf(widget)); }
|
|
|
|
void UiDriver::press(PointerButton button, Modifiers modifiers) {
|
|
mUi.dispatch(
|
|
Event::pointer(Event::Type::PointerDown, mPointer, { 0, 0 }, button).withModifiers(modifiers).withSample(mSample)
|
|
);
|
|
finishLayoutAnimations();
|
|
}
|
|
|
|
void UiDriver::release(PointerButton button, Modifiers modifiers) {
|
|
mUi.dispatch(
|
|
Event::pointer(Event::Type::PointerUp, mPointer, { 0, 0 }, button).withModifiers(modifiers).withSample(mSample)
|
|
);
|
|
finishLayoutAnimations();
|
|
}
|
|
|
|
void UiDriver::click(Vec2F windowPoint, Modifiers modifiers) {
|
|
moveTo(windowPoint);
|
|
press(PointerButton::Primary, modifiers);
|
|
release(PointerButton::Primary, modifiers);
|
|
}
|
|
|
|
void UiDriver::click(const Widget& widget, Modifiers modifiers) { click(centerOf(widget), modifiers); }
|
|
|
|
void UiDriver::setSlider(Slider& slider, halnf value) {
|
|
settle();
|
|
click(slider.windowPointForValue(value));
|
|
}
|
|
|
|
void UiDriver::drag(Vec2F from, Vec2F to, int steps) {
|
|
moveTo(from);
|
|
press();
|
|
for (int step = 1; step <= steps; step++) {
|
|
const halnf amount = (halnf) step / (halnf) steps;
|
|
moveTo(from + (to - from) * amount);
|
|
}
|
|
release();
|
|
}
|
|
|
|
void UiDriver::swipe(Vec2F from, Vec2F to, halnf durationMs, int steps) {
|
|
moveTo(from);
|
|
press();
|
|
for (int step = 1; step <= steps; step++) {
|
|
advance(durationMs / (halnf) steps);
|
|
moveTo(from + (to - from) * ((halnf) step / (halnf) steps));
|
|
}
|
|
release();
|
|
}
|
|
|
|
void UiDriver::wheel(halnf amount) {
|
|
mUi.dispatch(Event::wheel(mPointer, amount));
|
|
finishLayoutAnimations();
|
|
}
|
|
|
|
void UiDriver::key(InputID key, Modifiers modifiers) { holdKey(key, 0, modifiers); }
|
|
|
|
void UiDriver::holdKey(InputID key, int repeats, Modifiers modifiers) {
|
|
mUi.dispatch(Event::key(Event::Type::KeyDown, key).withModifiers(modifiers));
|
|
for (int repeat = 0; repeat < repeats; repeat++)
|
|
mUi.dispatch(Event::keyRepeat(key).withModifiers(modifiers));
|
|
mUi.dispatch(Event::key(Event::Type::KeyUp, key).withModifiers(modifiers));
|
|
finishLayoutAnimations();
|
|
}
|
|
|
|
void UiDriver::pressKey(InputID key, Modifiers modifiers) {
|
|
mUi.dispatch(Event::key(Event::Type::KeyDown, key).withModifiers(modifiers));
|
|
finishLayoutAnimations();
|
|
}
|
|
|
|
void UiDriver::releaseKey(InputID key, Modifiers modifiers) {
|
|
mUi.dispatch(Event::key(Event::Type::KeyUp, key).withModifiers(modifiers));
|
|
finishLayoutAnimations();
|
|
}
|
|
|
|
void UiDriver::type(const std::string& text) {
|
|
mUi.dispatch(Event::text(text));
|
|
finishLayoutAnimations();
|
|
}
|
|
|
|
void UiDriver::advance(halnf deltaMs) {
|
|
mClockMs += deltaMs;
|
|
mSample.timeMs = (uint32_t) mClockMs;
|
|
while (deltaMs > 0) {
|
|
const halnf step = deltaMs < kFrameMs ? deltaMs : kFrameMs;
|
|
mUi.advance(step);
|
|
deltaMs -= step;
|
|
}
|
|
}
|
|
|
|
void UiDriver::settle(halnf maximumMs) {
|
|
const int maximumFrames = (int) (maximumMs / kFrameMs);
|
|
|
|
for (int frame = 0; frame < maximumFrames && mUi.needsFrame(); frame++) {
|
|
mClockMs += kFrameMs;
|
|
mSample.timeMs = (uint32_t) mClockMs;
|
|
mUi.advance(kFrameMs);
|
|
mUi.paint(mPainter);
|
|
}
|
|
mPainter.clear();
|
|
}
|
|
|
|
void UiDriver::hoverFor(const Widget& widget, halnf durationMs) {
|
|
moveTo(widget);
|
|
advance(durationMs);
|
|
}
|
|
|
|
RecordingPainter& UiDriver::paint() {
|
|
mPainter.clear();
|
|
mUi.paint(mPainter);
|
|
return mPainter;
|
|
}
|
|
|
|
Vec2F UiDriver::centerOf(const Widget& widget) {
|
|
finishLayoutAnimations();
|
|
mUi.updateLayout();
|
|
return widget.windowRect().center();
|
|
}
|