diff --git a/src/qml/Config.qml b/src/qml/Config.qml index 2202350..763af3c 100644 --- a/src/qml/Config.qml +++ b/src/qml/Config.qml @@ -1,168 +1,105 @@ import QtQuick 2.0 import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 import QtQuick.Dialogs 1.0 import QtGStreamer 1.0 import org.kde.kamoso 3.0 import org.kde.kirigami 2.0 as Kirigami ColumnLayout { spacing: 1 clip: true + property alias header: view.header ScrollView { Layout.fillHeight: true Layout.fillWidth: true GridView { + id: view readonly property real delegateWidth: Kirigami.Units.gridUnit*4 readonly property int columnCount: Math.floor(width/delegateWidth) cellWidth: width/columnCount cellHeight: cellWidth header: Label { font.bold: true text: i18n("Effects") } model: ListModel { ListElement { filters: "identity" } ListElement { filters: "bulge" } ListElement { filters: "frei0r-filter-cartoon" } ListElement { filters: "frei0r-filter-twolay0r" } // ListElement { filters: "frei0r-filter-color-distance" } ListElement { filters: "dicetv" } ListElement { filters: "frei0r-filter-distort0r" } ListElement { filters: "edgetv" } ListElement { filters: "videoflip method=horizontal-flip" } ListElement { filters: "coloreffects preset=heat" } ListElement { filters: "videobalance saturation=0 ! agingtv" } ListElement { filters: "videobalance saturation=1.5 hue=-0.5" } // ListElement { filters: "frei0r-filter-invert0r" } ListElement { filters: "kaleidoscope" } ListElement { filters: "videobalance saturation=1.5 hue=+0.5" } ListElement { filters: "mirror" } ListElement { filters: "videobalance saturation=0" } ListElement { filters: "optv" } ListElement { filters: "pinch" } ListElement { filters: "quarktv" } ListElement { filters: "radioactv" } ListElement { filters: "revtv" } ListElement { filters: "rippletv" } ListElement { filters: "videobalance saturation=2" } ListElement { filters: "coloreffects preset=sepia" } ListElement { filters: "shagadelictv" } // ListElement { filters: "frei0r-filter-sobel" } ListElement { filters: "square" } ListElement { filters: "streaktv" } ListElement { filters: "stretch" } ListElement { filters: "frei0r-filter-delay0r delaytime=1" } ListElement { filters: "twirl" } ListElement { filters: "vertigotv" } ListElement { filters: "warptv" } ListElement { filters: "coloreffects preset=xray" } } delegate: MouseArea { id: delegateItem width: GridView.view.cellHeight-1 height: GridView.view.cellWidth-1 VideoItem { anchors.fill: parent PipelineItem { id: pipe function refreshVisible() { if (visible) { pipe.playing = true } pipe.playing = false } description: "filesrc location=\"" + webcam.sampleImage + "\" ! decodebin ! imagefreeze ! videoconvert ! " + model.filters + " name=last" property bool dirty: false onDescriptionChanged: dirty = true } onVisibleChanged: { pipe.refreshVisible() if (pipe.dirty) { pipe.dirty=false pipe.refresh() } } surface: pipe.surface } onClicked: { devicesModel.playingDevice.filters = model.filters } } } } - - Item { height: 15 } - - Label { - font.bold: true - text: i18n("Save to...") - } - - function pathOrUrl(url) { - var urlstr = url.toString(); - if (urlstr.indexOf("file://") == 0) { - return urlstr.substring(7); - } - return url; - } - - Button { - Layout.fillWidth: true - - iconName: "folder-pictures" - text: i18n("%1", pathOrUrl(config.saveUrl)) - onClicked: { - dirSelector.visible = true - } - - FileDialog { - id: dirSelector - title: i18n("Select a directory where to save your pictures") - folder: config.saveUrl - selectMultiple: false - selectExisting: true - selectFolder: true - - onFileUrlChanged: { - config.saveUrl = dirSelector.fileUrl - config.save() - } - } - } - Button { - Layout.fillWidth: true - - iconName: "folder-videos" - text: i18n("%1", pathOrUrl(config.saveVideos)) - onClicked: { - videoDirSelector.visible = true - } - - FileDialog { - id: videoDirSelector - title: i18n("Select a directory where to save your videos") - folder: config.saveVideos - selectMultiple: false - selectExisting: true - selectFolder: true - - onFileUrlChanged: { - config.saveVideos = videoDirSelector.fileUrl - config.save() - } - } - } - Item { - Layout.fillHeight: true - } } diff --git a/src/qml/ImagesView.qml b/src/qml/ImagesView.qml index 92713a2..e73cba7 100644 --- a/src/qml/ImagesView.qml +++ b/src/qml/ImagesView.qml @@ -1,238 +1,281 @@ import QtQml 2.2 import QtQuick 2.0 import QtQuick.Controls 1.2 import QtQuick.Layouts 1.1 import QtQuick.Dialogs 1.2 import org.kde.kamoso 3.0 import org.kde.purpose 1.0 import org.kde.kirigami 2.0 as Kirigami StackView { id: stack property string mimeFilter property alias nameFilter: view.nameFilter clip: true Component { id: headerComponent ColumnLayout { spacing: 0 Layout.maximumHeight: Kirigami.Units.gridUnit * 10 Image { fillMode: Image.PreserveAspectCrop Layout.fillWidth: true Layout.fillHeight: true source: "https://images.unsplash.com/photo-1478809956569-c7ce9654a947?dpr=1&auto=format&fit=crop&w=1500&h=971&q=80&cs=tinysrgb&crop=" smooth: true Kirigami.Heading { anchors { left: parent.left right: parent.right bottom: parent.bottom margins: Kirigami.Units.smallSpacing * 2 } level: 1 color: "white" elide: Text.ElideRight text: i18n("Share...") } } Repeater { model: view.selection delegate: Kirigami.AbstractListItem { id: delegate Layout.minimumHeight: Kirigami.Units.gridUnit * 3 spacing: 0 RowLayout { ImageThumbnail { Layout.fillHeight: true width: height path: modelData } Kirigami.Label { Layout.fillWidth: true text: modelData.substring(modelData.lastIndexOf('/')+1); elide: Text.ElideLeft } } } } } } Component { id: chooseShareComponent ColumnLayout { id: menu property var selection spacing: 0 Loader { Layout.fillWidth: true Layout.maximumHeight: item.Layout.maximumHeight sourceComponent: headerComponent } AlternativesView { id: altsView Layout.fillWidth: true Layout.fillHeight: true pluginType: "Export" inputData: { "urls": view.selection, "mimeType": stack.mimeFilter } verticalLayoutDirection: ListView.BottomToTop delegate: Kirigami.BasicListItem { label: display icon: model.iconName onClicked: altsView.createJob(index); } onFinished: stack.replace({ item: sharedComponent, properties: { text: output.url }, replace: true }) } Kirigami.Separator { Layout.fillWidth: true } Kirigami.BasicListItem { label: i18n("Back") onClicked: stack.pop() } } } Component { id: sharedComponent ColumnLayout { property alias text: field.text spacing: 0 Loader { Layout.fillWidth: true Layout.maximumHeight: item.Layout.maximumHeight sourceComponent: headerComponent } Item { Layout.fillHeight: true Layout.fillWidth: true } TextField { id: field Layout.fillWidth: true readOnly: true focus: true onTextChanged: { selectAll(); copy(); } } Item { Layout.fillHeight: true Layout.fillWidth: true } Kirigami.Label { Layout.fillHeight: true Layout.alignment: Qt.AlignCenter text: i18n("Media now exported") } Kirigami.Separator { Layout.fillWidth: true } Kirigami.BasicListItem { label: i18n("Back") onClicked: { stack.pop() } } } } initialItem: Item { ColumnLayout { anchors.fill: parent spacing: 0 - DirectoryView { - id: view - header: Image { - fillMode: Image.PreserveAspectCrop - width: view.width - height: Kirigami.Units.gridUnit * 10 - source: "https://images.unsplash.com/photo-1484781663516-4c4ca4b04a13?dpr=1&auto=format&fit=crop&w=1500&h=1021&q=80&cs=tinysrgb" - smooth: true - - Kirigami.Heading { - anchors { - left: parent.left - right: parent.right - bottom: parent.bottom - margins: Kirigami.Units.smallSpacing * 2 - } - level: 1 - color: "white" - elide: Text.ElideRight - text: i18n("Kamoso Gallery") + Label { + font.bold: true + text: i18n("Save to...") + } + + function pathOrUrl(url) { + var urlstr = url.toString(); + if (urlstr.indexOf("file://") == 0) { + return urlstr.substring(7); + } + return url; + } + + Button { + Layout.fillWidth: true + + iconName: "folder-pictures" + text: parent.pathOrUrl(config.saveUrl) + onClicked: { + dirSelector.visible = true + } + + FileDialog { + id: dirSelector + title: i18n("Select a directory where to save your pictures") + folder: config.saveUrl + selectMultiple: false + selectExisting: true + selectFolder: true + + onFileUrlChanged: { + config.saveUrl = dirSelector.fileUrl + config.save() + } + } + } + Button { + Layout.fillWidth: true + + iconName: "folder-videos" + text: parent.pathOrUrl(config.saveVideos) + onClicked: { + videoDirSelector.visible = true + } + + FileDialog { + id: videoDirSelector + title: i18n("Select a directory where to save your videos") + folder: config.saveVideos + selectMultiple: false + selectExisting: true + selectFolder: true + + onFileUrlChanged: { + config.saveVideos = videoDirSelector.fileUrl + config.save() } } + } + Item { + Layout.fillHeight: true + } + + DirectoryView { + id: view Layout.fillWidth: true Layout.fillHeight: true mimeFilter: [stack.mimeFilter] } Kirigami.Separator { Layout.fillWidth: true } Kirigami.BasicListItem { icon: "user-trash" label: i18n("Move to trash... (%1)", view.selection.length) visible: view.selection.length>0 onClicked: { trashDialog.visible = true } readonly property var p0: Dialog { id: trashDialog title: i18n("Move to trash...") Label { text: i18np("Are you sure you want to remove %1 file?", "Are you sure you want to remove %1 files?", view.selection.length) } standardButtons: StandardButton.Ok | StandardButton.Cancel onAccepted: { console.log("Trash, FFS!!", view.selection); webcam.trashFiles(view.selection); } onVisibleChanged: if (!visible) { view.selection = [] } } } Kirigami.BasicListItem { icon: "document-share" label: i18n("Share... (%1)", view.selection.length) onClicked: stack.push({ item: chooseShareComponent, properties: { selection: view.selection } }) visible: view.selection.length>0 } Kirigami.BasicListItem { icon: "folder-open" label: i18n("Open Folder...") onClicked: Qt.openUrlExternally(config.saveUrl) } } } } diff --git a/src/qml/Main.qml b/src/qml/Main.qml index 8c54ae3..72a8598 100644 --- a/src/qml/Main.qml +++ b/src/qml/Main.qml @@ -1,250 +1,271 @@ import QtQuick 2.0 import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 import QtGStreamer 1.0 import org.kde.kirigami 2.0 as Kirigami import org.kde.kamoso 3.0 Kirigami.ApplicationWindow { id: root width: 700 height: width*3/4 visible: true title: i18n("Kamoso") header: Item {} function awesomeAnimation(path) { // tada.x = visor.x // tada.y = 0 // tada.width = visor.width // tada.height = visor.height tada.source = "file://"+path tada.state = "go" tada.state = "done" // tada.visible = true } Image { id: tada z: 10 width: 10 height: 10 fillMode: Image.PreserveAspectFit states: [ State { name: "go" PropertyChanges { target: tada; x: visor.x } PropertyChanges { target: tada; y: visor.y } PropertyChanges { target: tada; width: visor.width } PropertyChanges { target: tada; height: visor.height } PropertyChanges { target: tada; opacity: 1 } }, State { name: "done" PropertyChanges { target: tada; x: 0 } PropertyChanges { target: tada; y: root.height } PropertyChanges { target: tada; width: deviceSelector.height } PropertyChanges { target: tada; height: deviceSelector.height } PropertyChanges { target: tada; opacity: 0.5 } } ] transitions: [ Transition { from: "go"; to: "done" NumberAnimation { target: tada properties: "width,height"; duration: 700; easing.type: Easing.InCubic } NumberAnimation { target: tada properties: "x,y"; duration: 700; easing.type: Easing.InCubic } NumberAnimation { target: tada properties: "opacity"; duration: 300 } } ] } Mode { id: photoMode mimes: "image/jpeg" checkable: false iconName: "shoot" text: i18n("Shoot") nameFilter: "picture_*" onTriggered: { whites.showAll() webcam.takePhoto() } Connections { target: webcam onPhotoTaken: awesomeAnimation(path) } } Mode { id: burstMode mimes: "image/jpeg" checkable: true iconName: "burst" text: i18n("Burst") property int photosTaken: 0 modeInfo: (photosTaken>0 ? i18np("1 photo", "%1 photos", photosTaken) : "") + (checked? "..." : "") nameFilter: "picture_*" onCheckedChanged: if (checked) { photosTaken = 0 } readonly property var smth: Timer { id: burstTimer running: burstMode.checked interval: 1000 repeat: true onTriggered: { whites.showAll() webcam.takePhoto() burstMode.photosTaken++; } } } Mode { id: videoMode mimes: "video/x-matroska" checkable: true iconName: "record" text: i18n("Record") modeInfo: webcam.recordingTime nameFilter: "video_*" onCheckedChanged: { webcam.isRecording = checked; } } - globalDrawer: Kirigami.OverlayDrawer { - edge: Qt.LeftEdge + contextDrawer: Kirigami.OverlayDrawer { + edge: Qt.RightEdge drawerOpen: false handleVisible: true modal: true leftPadding: 0 topPadding: 0 rightPadding: 0 bottomPadding: 0 contentItem: ImagesView { id: view implicitWidth: Kirigami.Units.gridUnit * 20 mimeFilter: root.pageStack.currentItem.actions.main.mimes nameFilter: root.pageStack.currentItem.actions.main.nameFilter } } - contextDrawer: Kirigami.OverlayDrawer { - edge: Qt.RightEdge + globalDrawer: Kirigami.OverlayDrawer { + edge: Qt.LeftEdge drawerOpen: false handleVisible: true modal: true width: Kirigami.Units.gridUnit * 20 - contentItem: Config {} + contentItem: Config { + header: Image { + fillMode: Image.PreserveAspectCrop + width: view.width + height: Kirigami.Units.gridUnit * 10 + source: "https://images.unsplash.com/photo-1484781663516-4c4ca4b04a13?dpr=1&auto=format&fit=crop&w=1500&h=1021&q=80&cs=tinysrgb" + smooth: true + + Kirigami.Heading { + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + margins: Kirigami.Units.smallSpacing * 2 + } + level: 1 + color: "white" + elide: Text.ElideRight + text: i18n("Kamoso Gallery") + } + } + } } pageStack.initialPage: Kirigami.Page { id: visor bottomPadding: 0 topPadding: 0 rightPadding: 0 leftPadding: 0 state: "shoot" states: [ State { name: "shoot" PropertyChanges { target: visor actions { left: videoMode.adoptAction main: photoMode right: burstMode.adoptAction } } }, State { name: "record" PropertyChanges { target: visor actions { left: photoMode.adoptAction main: videoMode right: burstMode.adoptAction } } }, State { name: "burst" PropertyChanges { target: visor actions { left: videoMode.adoptAction main: burstMode right: photoMode.adoptAction } } } ] Rectangle { anchors.fill: parent color: "black" z: -1 } ColumnLayout { id: deviceSelector spacing: 10 anchors.margins: 10 anchors.top: parent.top anchors.left: parent.left visible: devicesModel.count>1 Repeater { model: devicesModel delegate: Button { width: 30 iconName: "camera-web" tooltip: display onClicked: devicesModel.playingDeviceUdi = udi } } } VideoItem { id: video visible: devicesModel.count>0 surface: videoSurface1 anchors.fill: parent } AnimatedImage { anchors.fill: video visible: !video.visible source: visible ? "http://i.imgur.com/OEiQ6k9.gif" : "" } Text { anchors { horizontalCenter: parent.horizontalCenter top: parent.top margins: 20 } text: root.pageStack.currentItem.actions.main.modeInfo color: "white" styleColor: "black" font.pointSize: 20 style: Text.Outline } } }