14.7: Canvas Only, Toggle Fullscreen on F and Escape
This commit is contained in:
19 files changed
+416
-16
No files matched your search
+14
-6
@@ -253,7 +253,9 @@ ContentView::ContentView(EditorSession &session, ProjectController *projects, QW
|
||||
auto *middle = new QHBoxLayout;
|
||||
middle->setSpacing(0);
|
||||
middle->addWidget(rail);
|
||||
middle->addWidget(divider(QFrame::VLine, this));
|
||||
m_panels = {rail, divider(QFrame::VLine, this), new PanelResizeEdge(*m_layersPanel, this), m_layersPanel, divider(QFrame::HLine, this),
|
||||
divider(QFrame::HLine, this), new QWidget(this)};
|
||||
middle->addWidget(m_panels[1]);
|
||||
// The rulers frame the canvas above and left, Swift's stacks.
|
||||
auto *framed = new QWidget(this);
|
||||
auto *rulers = new QGridLayout(framed);
|
||||
@@ -267,7 +269,7 @@ ContentView::ContentView(EditorSession &session, ProjectController *projects, QW
|
||||
rulers->addWidget(canvas, 1, 1);
|
||||
middle->addWidget(framed, 1);
|
||||
m_layersPanel->setFixedWidth(int(layersPanelWidth()));
|
||||
middle->addWidget(new PanelResizeEdge(*m_layersPanel, this));
|
||||
middle->addWidget(m_panels[2]);
|
||||
middle->addWidget(m_layersPanel);
|
||||
m_dock->setObjectName(QStringLiteral("panelDock"));
|
||||
m_dock->setFixedWidth(FloatingPanel::dockedWidth);
|
||||
@@ -284,7 +286,7 @@ ContentView::ContentView(EditorSession &session, ProjectController *projects, QW
|
||||
m_spinner->setRange(0, 0);
|
||||
m_spinner->setTextVisible(false);
|
||||
m_spinner->setFixedSize(36, 6);
|
||||
auto *status = new QWidget(this);
|
||||
QWidget *status = m_panels[6];
|
||||
status->setObjectName(QStringLiteral("statusBar"));
|
||||
status->setFixedHeight(30);
|
||||
auto *statusRow = new QHBoxLayout(status);
|
||||
@@ -297,9 +299,9 @@ ContentView::ContentView(EditorSession &session, ProjectController *projects, QW
|
||||
// The hint takes what is left, and shrinks first.
|
||||
statusRow->addWidget(m_activity, 1);
|
||||
|
||||
m_column->addWidget(divider(QFrame::HLine, this));
|
||||
m_column->addWidget(m_panels[4]);
|
||||
m_column->addLayout(middle, 1);
|
||||
m_column->addWidget(divider(QFrame::HLine, this));
|
||||
m_column->addWidget(m_panels[5]);
|
||||
m_column->addWidget(status);
|
||||
|
||||
// The filter panel follows the session by itself.
|
||||
@@ -403,7 +405,12 @@ void ContentView::synchronize()
|
||||
m_maskAloneBadge->setLayerName(alone->name);
|
||||
m_maskAloneBadge->setVisible(alone.has_value());
|
||||
for (QWidget *ruler : m_rulers)
|
||||
ruler->setVisible(m_session.showsRulers() && document);
|
||||
ruler->setVisible(m_session.showsRulers() && document && !m_session.canvasOnly());
|
||||
for (QWidget *panel : m_panels)
|
||||
panel->setVisible(!m_session.canvasOnly());
|
||||
// A bar being made signals too: there is none yet.
|
||||
if (m_header)
|
||||
m_header->setVisible(!m_session.canvasOnly());
|
||||
m_zoom->setVisible(document.has_value());
|
||||
m_dimensions->setVisible(document.has_value());
|
||||
if (document) {
|
||||
@@ -457,6 +464,7 @@ void ContentView::showHeader(NavigationTool tool)
|
||||
return;
|
||||
m_shownTool = tool;
|
||||
delete m_header;
|
||||
m_header = nullptr;
|
||||
// A tool without a bar yet keeps the bar's height.
|
||||
if (tool == NavigationTool::idle)
|
||||
m_header = new ToolHeaderBar(QStringLiteral("Select a tool"), this);
|
||||
|
||||
@@ -79,6 +79,8 @@ private:
|
||||
MaskAloneBadge *const m_maskAloneBadge;
|
||||
// Swift 1.1.7's rulers: the corner, then the two strips.
|
||||
std::vector<QWidget *> m_rulers;
|
||||
// Bars and panels Canvas Only puts away.
|
||||
std::vector<QWidget *> m_panels;
|
||||
ProjectWorkspace *m_workspace = nullptr;
|
||||
// A document that appears hands the canvas the keys.
|
||||
bool m_hadDocument = false;
|
||||
|
||||
@@ -361,6 +361,24 @@ void EditorSession::zoomKeyboard(int step)
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::setCanvasOnly(bool only)
|
||||
{
|
||||
m_canvasOnly = only;
|
||||
notify();
|
||||
}
|
||||
|
||||
bool EditorSession::escapeHasNothingToCancel() const
|
||||
{
|
||||
return !m_textDraft && !m_levels && !m_brushStroke && !m_warpStroke && !m_lassoDraft && !m_shapeDraft && !m_gradientEdit && !m_cropRect
|
||||
&& !m_guideDrag && !m_transformEdit && !m_pixelMove;
|
||||
}
|
||||
|
||||
bool EditorSession::canToggleCanvasOnly() const
|
||||
{
|
||||
return m_document && !m_textDraft && !m_filterEdit && !m_hueSaturation && !m_levels && !m_colorPicker && !adjustmentEditingID() && !m_effectsEditing
|
||||
&& !m_colorRange && !selectionAmountOperation();
|
||||
}
|
||||
|
||||
void EditorSession::setShowsPixelGrid(bool shows)
|
||||
{
|
||||
m_showsPixelGrid = shows;
|
||||
|
||||
@@ -243,6 +243,13 @@ public:
|
||||
// The View menu's grid, shown from 800%.
|
||||
bool showsPixelGrid() const { return m_showsPixelGrid; }
|
||||
void setShowsPixelGrid(bool shows);
|
||||
// F: the canvas alone on black, panels and bars away.
|
||||
bool canvasOnly() const { return m_canvasOnly; }
|
||||
void setCanvasOnly(bool only);
|
||||
// Nothing in progress for Escape to cancel: it leaves fullscreen.
|
||||
bool escapeHasNothingToCancel() const;
|
||||
// A canvas, and no text or dialog for an F.
|
||||
bool canToggleCanvasOnly() const;
|
||||
// Swift's alias of View › Snap: moves, crops, drawing.
|
||||
bool snappingEnabled() const { return m_snapEnabled; }
|
||||
// The Eyedropper's ring: the sampled colour over the original.
|
||||
@@ -344,6 +351,7 @@ private:
|
||||
std::optional<QUuid> m_renamingLayerID;
|
||||
std::optional<QString> m_projectPath;
|
||||
bool m_showsPixelGrid = ToolDefaults::boolean(QStringLiteral("pixelGrid"), true);
|
||||
bool m_canvasOnly = false;
|
||||
// The layout grid starts off; guides show; rulers hide.
|
||||
bool m_showsGrid = ToolDefaults::boolean(QStringLiteral("grid"), false);
|
||||
LayoutGrid m_layoutGrid = LayoutGrid::stored();
|
||||
|
||||
@@ -77,6 +77,7 @@ CanvasView::DisplayState CanvasView::displayState() const
|
||||
const std::optional<TransformEdit> &edit = m_session.transformEdit();
|
||||
DisplayState state{.brushRevision = m_session.brushRevision(),
|
||||
.pixelGrid = m_session.showsPixelGrid(),
|
||||
.canvasOnly = m_session.canvasOnly(),
|
||||
.corners = edit ? edit->corners : std::nullopt,
|
||||
.documentID = document ? std::optional(document->id) : std::nullopt,
|
||||
.size = document ? std::optional(document->size()) : std::nullopt,
|
||||
@@ -210,8 +211,8 @@ void CanvasView::paintEvent(QPaintEvent *event)
|
||||
|
||||
void CanvasView::draw(QPainter &context, const QRectF &dirty)
|
||||
{
|
||||
// The surround is the palette's Base: Swift's 0.105 when dark.
|
||||
context.fillRect(dirty, palette().color(QPalette::Base));
|
||||
// The palette's Base, Swift's 0.105 when dark; Canvas Only, black.
|
||||
context.fillRect(dirty, m_session.canvasOnly() ? QColor(Qt::black) : palette().color(QPalette::Base));
|
||||
const std::optional<CanvasDocument> &document = m_session.document();
|
||||
if (!document)
|
||||
return;
|
||||
|
||||
@@ -111,6 +111,8 @@ private:
|
||||
// Every change to a stroke, so a press repaints.
|
||||
int brushRevision;
|
||||
bool pixelGrid;
|
||||
// Canvas Only's black surround.
|
||||
bool canvasOnly;
|
||||
// A distortion's corners: the warp follows them.
|
||||
std::optional<Corners> corners;
|
||||
std::optional<QUuid> documentID;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "IO/RecentProjects.h"
|
||||
#include "Logging.h"
|
||||
#include "UI/KeyboardShortcuts.h"
|
||||
#include "UI/ProjectWorkspaceView.h"
|
||||
#include <QApplication>
|
||||
#include <QMenu>
|
||||
|
||||
@@ -22,8 +23,8 @@ QString filterName(FilterKind kind)
|
||||
}
|
||||
}
|
||||
|
||||
CompositorMenus::CompositorMenus(ProjectWorkspace &workspace, QMenuBar &bar, QWidget &window)
|
||||
: QObject(&bar), m_workspace(workspace), m_window(window)
|
||||
CompositorMenus::CompositorMenus(ProjectWorkspace &workspace, QMenuBar &bar, ProjectWorkspaceView &window)
|
||||
: QObject(&bar), m_workspace(workspace), m_window(window), m_view(window)
|
||||
{
|
||||
QMenu *file = bar.addMenu(QStringLiteral("&File"));
|
||||
add(file, QStringLiteral("newCanvas"), QStringLiteral("New Canvas…"), QKeySequence(Qt::CTRL | Qt::Key_N), [this] { projects().newCanvas(); });
|
||||
@@ -126,6 +127,9 @@ CompositorMenus::CompositorMenus(ProjectWorkspace &workspace, QMenuBar &bar, QWi
|
||||
QMenu *view = bar.addMenu(QStringLiteral("&View"));
|
||||
add(view, QStringLiteral("searchCommands"), QStringLiteral("Search Commands…"), QKeySequence(Qt::CTRL | Qt::Key_F),
|
||||
[this, &bar] { m_palette.toggle(session(), bar, action(QStringLiteral("searchCommands"))); });
|
||||
// A plain F; a field takes its letters first.
|
||||
add(view, QStringLiteral("toggleFullscreen"), QStringLiteral("Toggle Fullscreen"), QKeySequence(Qt::Key_F), [this] { m_view.toggleCanvasOnly(); })
|
||||
->setCheckable(true);
|
||||
view->addSeparator();
|
||||
add(view, QStringLiteral("fit"), QStringLiteral("Fit Canvas"), QKeySequence(Qt::CTRL | Qt::Key_0), [this] { session().fit(); });
|
||||
add(view, QStringLiteral("actualPixels"), QStringLiteral("Actual Pixels"), QKeySequence(Qt::CTRL | Qt::Key_1), [this] { session().zoom(1); });
|
||||
@@ -291,6 +295,8 @@ QAction *CompositorMenus::add(QMenu *menu, const QString &name, const QString &t
|
||||
made->setObjectName(name);
|
||||
made->setProperty("originalShortcut", shortcut);
|
||||
made->setShortcut(ShortcutSettings::shared().menu(shortcut));
|
||||
// Keys still work while Canvas Only hides the bar.
|
||||
m_window.addAction(made);
|
||||
connect(made, &QAction::triggered, this, run);
|
||||
return made;
|
||||
}
|
||||
@@ -357,6 +363,8 @@ void CompositorMenus::synchronize()
|
||||
for (const char *name : {"fit", "actualPixels", "zoomIn", "zoomOut"})
|
||||
action(QString::fromLatin1(name))->setEnabled(drawn);
|
||||
action(QStringLiteral("pixelGrid"))->setChecked(s.showsPixelGrid());
|
||||
action(QStringLiteral("toggleFullscreen"))->setChecked(s.canvasOnly());
|
||||
action(QStringLiteral("toggleFullscreen"))->setEnabled(s.canToggleCanvasOnly());
|
||||
const bool opened = s.document().has_value();
|
||||
for (const auto &[name, value] : std::initializer_list<std::pair<const char *, bool>>{
|
||||
{"showGrid", s.showsGrid()}, {"showGuides", s.showsGuides()}, {"showRulers", s.showsRulers()}, {"snapEnabled", s.snapEnabled()},
|
||||
|
||||
@@ -9,11 +9,13 @@
|
||||
#include <QMenuBar>
|
||||
#include <QPointer>
|
||||
|
||||
class ProjectWorkspaceView;
|
||||
|
||||
// The menu bar: Swift's commands whose session functions exist.
|
||||
class CompositorMenus : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
CompositorMenus(ProjectWorkspace &workspace, QMenuBar &bar, QWidget &window);
|
||||
CompositorMenus(ProjectWorkspace &workspace, QMenuBar &bar, ProjectWorkspaceView &window);
|
||||
// The panel's hide moves the focus: stop listening first.
|
||||
~CompositorMenus() override { disconnect(qApp, nullptr, this, nullptr); }
|
||||
|
||||
@@ -35,6 +37,7 @@ private:
|
||||
|
||||
ProjectWorkspace &m_workspace;
|
||||
QWidget &m_window;
|
||||
ProjectWorkspaceView &m_view;
|
||||
FloatingPanel m_shortcutsPanel{QStringLiteral("keyboardShortcuts"), m_window};
|
||||
CommandPaletteController m_palette{m_window};
|
||||
QMetaObject::Connection m_sessionWatch;
|
||||
|
||||
@@ -138,7 +138,7 @@ const std::vector<ShortcutDefinition> &ShortcutDefinition::all()
|
||||
entry("Move Layer Down", "[", 1, true), entry("Merge Layers", "e", 1, true), entry("Show Grid", "'", 1, true),
|
||||
entry("Show Guides", ";", 1, true), entry("Show Rulers", "r", 1, true), entry("Snap", ";", 9, true), entry("Lock Guides", ";", 3, true)};
|
||||
const std::vector<std::pair<const char *, QString>> tools{
|
||||
{"Select tool", "a"}, {"Move / Transform tool", "v"}, {"Hand tool", "h"}, {"Zoom tool", "z"}, {"Brush tool", "b"},
|
||||
{"Toggle Fullscreen: the canvas alone on black (Esc also leaves)", "f"}, {"Select tool", "a"}, {"Move / Transform tool", "v"}, {"Hand tool", "h"}, {"Zoom tool", "z"}, {"Brush tool", "b"},
|
||||
{"Eraser", "e"}, {"Spot Healing", "j"}, {"Clone Stamp", "s"}, {"Type tool", "t"}, {"Gradient tool", "g"},
|
||||
{"Shape tool", "u"}, {"Eyedropper tool", "i"}, {"Marquee / cycle shape", "m"}, {"Magic", "w"},
|
||||
{"Lasso / cycle mode", "l"}, {"Blur / Smudge / Liquify", "r"}, {"Crop tool", "c"}, {"Swap foreground/background", "x"},
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
#include "UI/ProjectWorkspaceView.h"
|
||||
#include "Logging.h"
|
||||
#include <QApplication>
|
||||
#include <QCloseEvent>
|
||||
#include <QDialog>
|
||||
#include <QKeyEvent>
|
||||
#include <QLineEdit>
|
||||
#include <QMenuBar>
|
||||
|
||||
namespace {
|
||||
QAction *action(QToolBar *bar, const QString &text, const QString &help, const QString &name)
|
||||
@@ -41,6 +47,7 @@ ProjectWorkspaceView::ProjectWorkspaceView(ProjectWorkspace &workspace, QWidget
|
||||
connect(m_zoomIn, &QAction::triggered, this, [this] { m_workspace.current().session.zoomKeyboard(1); });
|
||||
connect(m_zoomOut, &QAction::triggered, this, [this] { m_workspace.current().session.zoomKeyboard(-1); });
|
||||
connect(&m_workspace, &ProjectWorkspace::changed, this, &ProjectWorkspaceView::synchronize);
|
||||
qApp->installEventFilter(this);
|
||||
synchronize();
|
||||
}
|
||||
|
||||
@@ -96,6 +103,47 @@ void ProjectWorkspaceView::synchronizeSession()
|
||||
setWindowFilePath(session.projectPath().value_or(QString()));
|
||||
}
|
||||
|
||||
void ProjectWorkspaceView::toggleCanvasOnly()
|
||||
{
|
||||
EditorSession &session = m_workspace.current().session;
|
||||
if (!session.canvasOnly()) {
|
||||
// Already fullscreen, it has the screen: only bars go.
|
||||
if (!windowState().testFlag(Qt::WindowFullScreen)) {
|
||||
m_beforeCanvasOnly = windowState();
|
||||
menuBar()->hide();
|
||||
setWindowState(windowState() | Qt::WindowFullScreen);
|
||||
}
|
||||
m_toolbar->hide();
|
||||
session.setCanvasOnly(true);
|
||||
} else {
|
||||
session.setCanvasOnly(false);
|
||||
if (m_beforeCanvasOnly) {
|
||||
menuBar()->show();
|
||||
setWindowState(m_beforeCanvasOnly.value());
|
||||
m_beforeCanvasOnly.reset();
|
||||
}
|
||||
m_toolbar->show();
|
||||
}
|
||||
qCInfo(lcApp) << "canvas only" << session.canvasOnly();
|
||||
}
|
||||
|
||||
bool ProjectWorkspaceView::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
if (event->type() != QEvent::KeyPress || static_cast<QKeyEvent *>(event)->key() != Qt::Key_Escape)
|
||||
return QMainWindow::eventFilter(watched, event);
|
||||
const Qt::KeyboardModifiers held = static_cast<QKeyEvent *>(event)->modifiers() & (Qt::ControlModifier | Qt::MetaModifier | Qt::AltModifier | Qt::ShiftModifier);
|
||||
const EditorSession &session = m_workspace.current().session;
|
||||
QWidget *focus = QApplication::focusWidget();
|
||||
// A field's Escape is its own, and a docked panel's.
|
||||
bool typing = qobject_cast<QLineEdit *>(focus) != nullptr;
|
||||
for (QWidget *each = focus; each && !typing; each = each->parentWidget())
|
||||
typing = qobject_cast<QDialog *>(each) != nullptr;
|
||||
if (held || !session.canvasOnly() || !session.escapeHasNothingToCancel() || QApplication::activeWindow() != this || typing)
|
||||
return QMainWindow::eventFilter(watched, event);
|
||||
toggleCanvasOnly();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProjectWorkspaceView::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
// The workspace asks first, then closes us with its mark.
|
||||
|
||||
@@ -15,9 +15,13 @@ public:
|
||||
|
||||
ContentView *content() const { return m_content; }
|
||||
ProjectTabStrip *tabs() const { return m_tabs; }
|
||||
// Canvas Only: the window fills the screen, bars away.
|
||||
void toggleCanvasOnly();
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
// Escape leaves fullscreen once nothing is left to cancel.
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
|
||||
private:
|
||||
void synchronize();
|
||||
@@ -35,5 +39,7 @@ private:
|
||||
ContentView *m_content = nullptr;
|
||||
// Held until its editor goes, as Swift's views hold sessions.
|
||||
std::shared_ptr<ProjectTab> m_shownTab;
|
||||
// The window's state before Canvas Only, to put back.
|
||||
std::optional<Qt::WindowStates> m_beforeCanvasOnly;
|
||||
QMetaObject::Connection m_sessionWatch;
|
||||
};
|
||||
Reference in new issue
Block a user