mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-06-03 03:11:56 +02:00
[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:
parent
9cc70c8cff
commit
c190f418b7
4 changed files with 280 additions and 27 deletions
85
meshroom/ui/qml/ImageGallery.qml
Normal file
85
meshroom/ui/qml/ImageGallery.qml
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
105
meshroom/ui/qml/Viewer/Viewer2D.qml
Normal file
105
meshroom/ui/qml/Viewer/Viewer2D.qml
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
3
meshroom/ui/qml/Viewer/qmldir
Normal file
3
meshroom/ui/qml/Viewer/qmldir
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module Viewer
|
||||||
|
|
||||||
|
Viewer2D 1.0 Viewer2D.qml
|
|
@ -1,5 +1,6 @@
|
||||||
import QtQuick 2.7
|
import QtQuick 2.7
|
||||||
import QtQuick.Controls 2.3
|
import QtQuick.Controls 2.3
|
||||||
|
import QtQuick.Controls 1.4 as Controls1 // For SplitView
|
||||||
import QtQuick.Layouts 1.1
|
import QtQuick.Layouts 1.1
|
||||||
import QtQuick.Window 2.3
|
import QtQuick.Window 2.3
|
||||||
import QtQml.Models 2.2
|
import QtQml.Models 2.2
|
||||||
|
@ -121,6 +122,31 @@ ApplicationWindow {
|
||||||
return saved
|
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 {
|
Action {
|
||||||
id: undoAction
|
id: undoAction
|
||||||
|
|
||||||
|
@ -184,43 +210,77 @@ ApplicationWindow {
|
||||||
ToolTip.text: redoAction.tooltip
|
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.fill: parent
|
||||||
anchors.margins: 4
|
anchors.margins: 4
|
||||||
Row {
|
orientation: Qt.Vertical
|
||||||
spacing: 1
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
Button {
|
ImageGallery {
|
||||||
text: "Execute"
|
property variant node: _reconstruction.graph.nodes.get("CameraInit_1")
|
||||||
enabled: _reconstruction.graph.nodes.count && !_reconstruction.computing
|
model: node ? node.attribute("viewpoints").value : undefined
|
||||||
onClicked: _reconstruction.execute(null)
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
text: "Stop"
|
|
||||||
enabled: _reconstruction.computing
|
|
||||||
onClicked: _reconstruction.stopExecution()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GraphEditor {
|
|
||||||
id: graphEditor
|
|
||||||
graph: _reconstruction.graph
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: parent.height * 0.3
|
implicitHeight: Math.round(parent.height * 0.5)
|
||||||
Layout.margins: 10
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Controls1.SplitView {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
active: graphEditor.selectedNode != null
|
orientation: Qt.Horizontal
|
||||||
sourceComponent: Component {
|
|
||||||
AttributeEditor {
|
ColumnLayout {
|
||||||
node: graphEditor.selectedNode
|
Layout.fillHeight: true
|
||||||
// Disable editor when computing
|
Layout.fillWidth: true
|
||||||
enabled: !_reconstruction.computing
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue