[ui] wip: add ImageGallery + Viewer2D to main layout

* ImageGallery displays images from CameraInit.viewpoints
* support drag&drop of images
* Viewer2D displays and allows basic manipulation of currently selected image
This commit is contained in:
Yann Lanthony 2017-11-14 18:14:44 +01:00
parent 9cc70c8cff
commit c190f418b7
4 changed files with 280 additions and 27 deletions

View file

@ -0,0 +1,85 @@
import QtQuick 2.7
import QtQuick.Controls 2.3
import QtQuick.Controls 1.4 as Controls1 // For SplitView
import QtQuick.Layouts 1.3
import Qt.labs.platform 1.0 as Platform
import "Viewer" 1.0
Item {
property alias model: grid.model
implicitWidth: 300
implicitHeight: 400
clip: true
SystemPalette {
id: palette
}
Controls1.SplitView {
anchors.fill: parent
GridView {
id: grid
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumWidth: cellWidth
cellWidth: 160
cellHeight: 120
ScrollBar.vertical: ScrollBar {}
keyNavigationEnabled: true
highlightFollowsCurrentItem: true
focus: true
delegate: Item {
property url source: object.value.get("path").value
width: grid.cellWidth
height: grid.cellHeight
Image {
anchors.fill: parent
anchors.margins: 8
source:parent.source
sourceSize: Qt.size(150, 150)
asynchronous: true
autoTransform: true
fillMode: Image.PreserveAspectFit
}
Rectangle {
color: palette.midlight
anchors.fill: parent
anchors.margins: 3
border.color: palette.highlight
border.width: imageMA.containsMouse || grid.currentIndex == index ? 2 : 0
z: -1
MouseArea {
id: imageMA
anchors.fill: parent
hoverEnabled: true
onClicked: {
grid.currentIndex = index
grid.forceActiveFocus()
}
}
}
}
DropArea {
id: dropArea
anchors.fill: parent
// TODO: onEntered: call specific method to filter files based on extension
onDropped: {
_reconstruction.handleFilesDrop(drop)
}
}
}
Item {
implicitWidth: Math.round(parent.width * 0.4)
Layout.minimumWidth: 40
Viewer2D {
anchors.fill: parent
anchors.margins: 10
source: grid.currentItem.source
}
}
}
}

View file

@ -0,0 +1,105 @@
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
FocusScope {
id: root
clip: true
property alias source: image.source
// slots
Keys.onPressed: {
if(event.key == Qt.Key_F) {
root.fit();
event.accepted = true;
}
}
// functions
function fit() {
if(image.status != Image.Ready)
return;
image.scale = Math.min(root.width/image.width, root.height/image.height)
image.x = Math.max((root.width-image.width*image.scale)*0.5, 0)
image.y = Math.max((root.height-image.height*image.scale)*0.5, 0)
}
// context menu
property Component contextMenu: Menu {
MenuItem {
text: "Fit"
onTriggered: fit()
}
MenuItem {
text: "Zoom 100%"
onTriggered: image.scale = 1
}
}
// placeholder, visible when image isn't ready
Rectangle {
anchors.centerIn: parent
width: Math.min(parent.width, parent.height*1.5) * 0.95 // 5% margin
height: Math.min(parent.height, parent.width*1.5) * 0.95 // 5% margin
color: "transparent"
border.color: "#333"
visible: image.status != Image.Ready
}
// image
Image {
id: image
transformOrigin: Item.TopLeft
asynchronous: true
smooth: false
fillMode: Image.PreserveAspectFit
autoTransform: true
onWidthChanged: if(status==Image.Ready) fit()
}
// busy indicator
BusyIndicator {
anchors.centerIn: parent
running: image.status === Image.Loading
}
// mouse area
MouseArea {
anchors.fill: parent
property double factor: 1.2
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onPressed: {
root.forceActiveFocus();
if(mouse.button & Qt.MiddleButton)
drag.target = image // start drag
}
onReleased: {
drag.target = undefined // stop drag
if(mouse.button & Qt.RightButton) {
var menu = contextMenu.createObject(root);
menu.x = mouse.x;
menu.y = mouse.y;
menu.open()
}
}
onWheel: {
var zoomFactor = wheel.angleDelta.y > 0 ? factor : 1/factor
if(Math.min(image.width*image.scale*zoomFactor, image.height*image.scale*zoomFactor) < 10)
return
var point = mapToItem(image, wheel.x, wheel.y)
image.x += (1-zoomFactor) * point.x * image.scale
image.y += (1-zoomFactor) * point.y * image.scale
image.scale *= zoomFactor
}
}
// zoom label
Label {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: 4
text: image.scale.toFixed(1) + "x"
state: "xsmall"
}
}

View file

@ -0,0 +1,3 @@
module Viewer
Viewer2D 1.0 Viewer2D.qml

View file

@ -1,5 +1,6 @@
import QtQuick 2.7
import QtQuick.Controls 2.3
import QtQuick.Controls 1.4 as Controls1 // For SplitView
import QtQuick.Layouts 1.1
import QtQuick.Window 2.3
import QtQml.Models 2.2
@ -121,6 +122,31 @@ ApplicationWindow {
return saved
}
Dialog {
// Popup displayed while the application
// is busy building intrinsics while importing images
id: buildingIntrinsicsDialog
modal: true
x: _window.width / 2 - width/2
y: _window.height / 2 - height/2
visible: _reconstruction.buildingIntrinsics
closePolicy: Popup.NoAutoClose
title: "Import Images"
ColumnLayout {
anchors.fill: parent
Label {
text: "Extracting images metadata... "
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
}
ProgressBar {
indeterminate: true
Layout.fillWidth: true
}
}
}
Action {
id: undoAction
@ -184,43 +210,77 @@ ApplicationWindow {
ToolTip.text: redoAction.tooltip
}
}
Menu {
title: "View"
Action {
text: "Fullscreen"
checkable: true
checked: _window.visibility == ApplicationWindow.FullScreen
shortcut: "Ctrl+F"
onTriggered: _window.visibility == ApplicationWindow.FullScreen ? _window.showNormal() : showFullScreen()
}
}
}
ColumnLayout {
Controls1.SplitView {
anchors.fill: parent
anchors.margins: 4
Row {
spacing: 1
Layout.fillWidth: true
orientation: Qt.Vertical
Button {
text: "Execute"
enabled: _reconstruction.graph.nodes.count && !_reconstruction.computing
onClicked: _reconstruction.execute(null)
}
Button {
text: "Stop"
enabled: _reconstruction.computing
onClicked: _reconstruction.stopExecution()
}
}
GraphEditor {
id: graphEditor
graph: _reconstruction.graph
ImageGallery {
property variant node: _reconstruction.graph.nodes.get("CameraInit_1")
model: node ? node.attribute("viewpoints").value : undefined
Layout.fillWidth: true
Layout.preferredHeight: parent.height * 0.3
Layout.margins: 10
implicitHeight: Math.round(parent.height * 0.5)
}
Loader {
Controls1.SplitView {
Layout.fillWidth: true
Layout.fillHeight: true
active: graphEditor.selectedNode != null
sourceComponent: Component {
AttributeEditor {
node: graphEditor.selectedNode
// Disable editor when computing
enabled: !_reconstruction.computing
orientation: Qt.Horizontal
ColumnLayout {
Layout.fillHeight: true
Layout.fillWidth: true
Layout.margins: 10
Row {
spacing: 1
Layout.fillWidth: true
Button {
text: "Execute"
enabled: _reconstruction.graph.nodes.count && !_reconstruction.computing
onClicked: _reconstruction.execute(null)
}
Button {
text: "Stop"
enabled: _reconstruction.computing
onClicked: _reconstruction.stopExecution()
}
}
GraphEditor {
id: graphEditor
graph: _reconstruction.graph
Layout.fillHeight: true
Layout.fillWidth: true
}
}
Item {
implicitHeight: Math.round(parent.height * 0.2)
implicitWidth: Math.round(parent.width * 0.3)
Loader {
anchors.fill: parent
anchors.margins: 10
active: graphEditor.selectedNode != null
sourceComponent: Component {
AttributeEditor {
node: graphEditor.selectedNode
// Disable editor when computing
enabled: !_reconstruction.computing
}
}
}
}
}