Skip to content

Commit

Permalink
Add Opacity dialog
Browse files Browse the repository at this point in the history
Fixes #108
  • Loading branch information
mitchcurtis committed Jan 3, 2019
1 parent 429f2d5 commit 306247e
Show file tree
Hide file tree
Showing 22 changed files with 417 additions and 15 deletions.
9 changes: 9 additions & 0 deletions app/qml/main.qml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ ApplicationWindow {
id: menuBar
canvas: window.canvas
hueSaturationDialog: hueSaturationDialog
opacityDialog: opacityDialog
canvasSizePopup: canvasSizePopup
imageSizePopup: imageSizePopup
moveContentsDialog: moveContentsDialog
Expand Down Expand Up @@ -418,6 +419,14 @@ ApplicationWindow {
canvas: window.canvas
}

Ui.OpacityDialog {
id: opacityDialog
parent: Overlay.overlay
anchors.centerIn: parent
project: projectManager.project
canvas: window.canvas
}

Ui.CanvasSizePopup {
id: canvasSizePopup
parent: Overlay.overlay
Expand Down
1 change: 1 addition & 0 deletions app/qml/qml.qbs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Group {
"ui/NewLayeredImageProjectPopup.qml",
"ui/NewProjectPopup.qml",
"ui/NewTilesetProjectPopup.qml",
"ui/OpacityDialog.qml",
"ui/OptionsDialog.qml",
"ui/Panel.qml",
"ui/ProjectTemplateButton.qml",
Expand Down
8 changes: 8 additions & 0 deletions app/qml/ui/+nativemenubar/MenuBar.qml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Item {
readonly property bool isImageProjectType: projectType === Project.ImageType || projectType === Project.LayeredImageType

property var hueSaturationDialog
property var opacityDialog
property var canvasSizePopup
property var imageSizePopup
property var moveContentsDialog
Expand Down Expand Up @@ -255,6 +256,13 @@ Item {
enabled: isImageProjectType && canvas && canvas.hasSelection
onTriggered: hueSaturationDialog.open()
}

Platform.MenuItem {
objectName: "opacityMenuItem"
text: qsTr("Opacity...")
enabled: isImageProjectType && canvas && canvas.hasSelection
onTriggered: opacityDialog.open()
}
}

Platform.MenuSeparator {}
Expand Down
4 changes: 4 additions & 0 deletions app/qml/ui/DoubleTextField.qml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ TextField {
property string propertyName
signal valueModified

function clamp(value) {
return Math.max(-1, Math.min(value, 1))
}

Keys.onDownPressed: {
let newValue = clamp(propertySource[propertyName] - 0.01)
if (propertySource[propertyName] !== newValue) {
Expand Down
8 changes: 4 additions & 4 deletions app/qml/ui/HueSaturationDialog.qml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import QtQuick.Controls 2.5

import App 1.0

import "." as Ui

Dialog {
id: root
objectName: "hueSaturationDialog"
Expand All @@ -39,6 +41,7 @@ Dialog {
property real hslHue
property real hslSaturation
property real hslLightness
property real hslAlpha

readonly property real sliderStepSize: 0.001

Expand All @@ -57,15 +60,12 @@ Dialog {
canvas.modifySelectionHsl(hslHue, hslSaturation, hslLightness)
}

function clamp(value) {
return Math.max(-1, Math.min(value, 1))
}

onAboutToShow: {
if (project) {
hslHue = 0
hslSaturation = 0
hslLightness = 0
hslAlpha = 0

hueTextField.forceActiveFocus()

Expand Down
8 changes: 8 additions & 0 deletions app/qml/ui/MenuBar.qml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Controls.MenuBar {
readonly property bool isImageProjectType: projectType === Project.ImageType || projectType === Project.LayeredImageType

property var hueSaturationDialog
property var opacityDialog
property var canvasSizePopup
property var imageSizePopup
property var moveContentsDialog
Expand Down Expand Up @@ -271,6 +272,13 @@ Controls.MenuBar {
enabled: isImageProjectType && canvas && canvas.hasSelection
onTriggered: hueSaturationDialog.open()
}

MenuItem {
objectName: "opacityMenuItem"
text: qsTr("Opacity...")
enabled: isImageProjectType && canvas && canvas.hasSelection
onTriggered: opacityDialog.open()
}
}

MenuSeparator {}
Expand Down
195 changes: 195 additions & 0 deletions app/qml/ui/OpacityDialog.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/*
Copyright 2019, Mitch Curtis
This file is part of Slate.
Slate is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Slate is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Slate. If not, see <http://www.gnu.org/licenses/>.
*/

import QtQuick 2.12
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.5

import App 1.0

import "." as Ui

Dialog {
id: root
objectName: "opacityDialog"
title: qsTr("Opacity")
modal: true
dim: false
focus: true

property Project project
property ImageCanvas canvas

property real hslAlpha

readonly property real sliderStepSize: 0.001

TextMetrics {
id: valueTextMetrics
font: opacityTextField.font
text: "-0.0001"
}

function modifySelectionHsl() {
var flags = ImageCanvas.DefaultAlphaAdjustment
if (doNotModifyFullyTransparentPixelsCheckBox.checked)
flags |= ImageCanvas.DoNotModifyFullyTransparentPixels
if (doNotModifyFullyOpaquePixelsCheckBox.checked)
flags |= ImageCanvas.DoNotModifyFullyOpaquePixels
canvas.modifySelectionHsl(0, 0, 0, hslAlpha, flags)
}

onAboutToShow: {
if (project) {
hslAlpha = 0

opacityTextField.forceActiveFocus()

canvas.beginModifyingSelectionHsl()
}
}

onClosed: {
canvas.forceActiveFocus()
if (canvas.adjustingImage) {
// The dialog can be closed in two ways, so it's easier just to discard any adjustment here.
canvas.endModifyingSelectionHsl(ImageCanvas.RollbackAdjustment)
}
}

contentItem: GridLayout {
columns: 3
columnSpacing: 24
rowSpacing: 0

Label {
text: qsTr("Opacity")

Layout.fillWidth: true
}
Slider {
id: opacitySlider
objectName: root.objectName + "OpacitySlider"
from: -1
to: 1
value: hslAlpha
stepSize: sliderStepSize
leftPadding: 0
rightPadding: 0

ToolTip.text: qsTr("Changes the opacity of the image")

onMoved: {
hslAlpha = value
modifySelectionHsl()
}

background: Item {
// Default Material values.
implicitWidth: 200
implicitHeight: 48

HorizontalGradientRectangle {
width: parent.width
gradient: Gradient {
GradientStop {
position: 0
color: "transparent"
}
GradientStop {
position: 1
color: Ui.CanvasColours.focusColour
}
}
anchors.verticalCenter: parent.verticalCenter
}
}
}
DoubleTextField {
id: opacityTextField
objectName: root.objectName + "OpacityTextField"
propertySource: root
propertyName: "hslAlpha"
// If the maximum length matches the text metrics text, the sign
// character will sometimes be slightly cut off, so account for that.
maximumLength: valueTextMetrics.text.length - 1

Layout.maximumWidth: valueTextMetrics.width
Layout.fillWidth: true

// We call this here instead of just doing it in e.g. onHslHueChanged
// because that would require us to block changes that occur when the
// HSL property values are reset upon showing the dialog. Modifying the
// selection only on user interaction instead feels nicer.
onValueModified: modifySelectionHsl()
}

CheckBox {
id: doNotModifyFullyTransparentPixelsCheckBox
objectName: "doNotModifyFullyTransparentPixelsCheckBox"
text: qsTr("Do not modify fully transparent pixels")
checked: true

Layout.columnSpan: 3

ToolTip.text: qsTr("Only change the alpha if it's non-zero to prevent fully transparent pixels from gaining opacity.")
ToolTip.visible: hovered
ToolTip.delay: toolTipDelay

onClicked: modifySelectionHsl()
}

CheckBox {
id: doNotModifyFullyOpaquePixelsCheckBox
objectName: "doNotModifyFullyOpaquePixelsCheckBox"
text: qsTr("Do not modify fully opaque pixels")
checked: true

Layout.columnSpan: 3

ToolTip.text: qsTr("Only change the alpha if it's less than one to prevent fully opaque pixels from losing opacity.")
ToolTip.visible: hovered
ToolTip.delay: toolTipDelay

onClicked: modifySelectionHsl()
}
}

footer: DialogButtonBox {
Button {
objectName: "opacityDialogOkButton"
text: qsTr("OK")

DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole

onClicked: {
canvas.endModifyingSelectionHsl(ImageCanvas.CommitAdjustment)
root.close()
}
}
Button {
objectName: "opacityDialogCancelButton"
text: qsTr("Cancel")

DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole

onClicked: root.close()
}
}
}
8 changes: 5 additions & 3 deletions lib/imagecanvas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1840,20 +1840,22 @@ void ImageCanvas::beginModifyingSelectionHsl()
emit adjustingImageChanged();
}

void ImageCanvas::modifySelectionHsl(qreal hue, qreal saturation, qreal lightness)
void ImageCanvas::modifySelectionHsl(qreal hue, qreal saturation, qreal lightness, qreal alpha,
AlphaAdjustmentFlags alphaAdjustmentFlags)
{
if (!isAdjustingImage()) {
qWarning() << "Not adjusting an image; can't modify selection's HSL";
return;
}

qCDebug(lcImageCanvasSelection).nospace() << "modifying HSL of selection"
<< mSelectionArea << " with h=" << hue << " s=" << saturation << " l=" << lightness;
<< mSelectionArea << " with h=" << hue << " s=" << saturation << " l=" << lightness << " a=" << alpha
<< "alpha flags=" << alphaAdjustmentFlags;

// Copy the original so we don't just modify the result of the last adjustment (if any).
mSelectionContents = mSelectionContentsBeforeImageAdjustment;

Utils::modifyHsl(mSelectionContents, hue, saturation, lightness);
Utils::modifyHsl(mSelectionContents, hue, saturation, lightness, alpha, alphaAdjustmentFlags);

// Set this so that the check in shouldDrawSelectionPreviewImage() evaluates to true.
setLastSelectionModification(SelectionHsl);
Expand Down
17 changes: 16 additions & 1 deletion lib/imagecanvas.h
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,20 @@ class SLATE_EXPORT ImageCanvas : public QQuickItem

Q_ENUM(AdjustmentAction)

enum AlphaAdjustmentOption {
// Modify the alpha regardless of what its current value is.
DefaultAlphaAdjustment = 0x00,
// Only change the alpha if it's non-zero to prevent fully transparent
// pixels (#00000000) gaining opacity.
DoNotModifyFullyTransparentPixels = 0x01,
// Only change the alpha if it's less than one to prevent fully opaque
// pixels (#FF000000) losing opacity.
DoNotModifyFullyOpaquePixels = 0x02
};

Q_DECLARE_FLAGS(AlphaAdjustmentFlags, AlphaAdjustmentOption)
Q_ENUM(AlphaAdjustmentFlags)

signals:
void projectChanged();
void zoomLevelChanged();
Expand Down Expand Up @@ -344,7 +358,8 @@ public slots:
void flipSelection(Qt::Orientation orientation);
void rotateSelection(int angle);
void beginModifyingSelectionHsl();
void modifySelectionHsl(qreal hue, qreal saturation, qreal lightness);
void modifySelectionHsl(qreal hue, qreal saturation, qreal lightness, qreal alpha = 0.0,
AlphaAdjustmentFlags alphaAdjustmentFlags = DefaultAlphaAdjustment);
void endModifyingSelectionHsl(AdjustmentAction adjustmentAction);
void copySelection();
void paste();
Expand Down
Loading

0 comments on commit 306247e

Please sign in to comment.