tabs
+
+ currentIndex: 0
+
+ background: Rectangle {
+ objectName: "background"
+ width: control.tabWidth
+ height: 2
+ radius: 1
+ color: "white"
+ x: control.tabWidth * control.currentIndex
+ y: control.height
+
+ Behavior on x {
+ NumberAnimation {
+ duration: 120
+ easing.type: Easing.InOutQuad
+ }
+ }
+ }
+
+ Repeater {
+ model: control.tabs
+ delegate: TabButton {
+ required property var modelData
+ required property var index
+
+ objectName: "button" + index
+
+ width: control.tabWidth
+ contentItem: Text {
+ objectName: "text"
+ text: modelData
+ color: parent.enabled ? "#ffffff" : "#666666"
+ font.family: Constants.regularFontFamily
+ font.pointSize: 11
+ font.weight: parent.enabled && parent.checked ? Font.Bold : Font.Normal
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ background: Item {
+ }
+
+ HoverHandler {
+ objectName: "hoverHandler"
+ cursorShape: Qt.PointingHandCursor
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/qml/common/Option.qml b/qml/common/Option.qml
index bbfa2d6..b199756 100644
--- a/qml/common/Option.qml
+++ b/qml/common/Option.qml
@@ -7,7 +7,10 @@ Pane {
required property string label
property string description
- required property Component control
+ // required property Component control
+ property Component control
+
+ property bool isSubItem: false
background: Item {
}
@@ -18,15 +21,29 @@ Pane {
RowLayout {
anchors.fill: parent
+ Text {
+ Layout.fillHeight: true
+ visible: root.isSubItem
+ leftPadding: 8
+ rightPadding: 8
+ text: "\ue5da"
+ font.family: Constants.symbolFontFamily
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ font.pixelSize: 24
+ color: ColorPalette.neutral600
+ }
+
ColumnLayout {
Layout.fillHeight: true
Layout.fillWidth: true
Layout.horizontalStretchFactor: 3
+
Text {
Layout.fillWidth: true
text: root.label
- color: "white"
- font.pointSize: 12
+ color: ColorPalette.neutral100
+ font.pixelSize: 15
Layout.alignment: Qt.AlignLeft
font.family: Constants.regularFontFamily
font.weight: Font.DemiBold
@@ -36,11 +53,12 @@ Pane {
Layout.fillWidth: true
visible: root.description !== ""
text: root.description
- font.pointSize: 11
+ font.pixelSize: 13
Layout.alignment: Qt.AlignLeft
font.family: Constants.regularFontFamily
+ // font.weight: Font.
wrapMode: Text.WordWrap
- color: "#c1c1c1"
+ color: ColorPalette.neutral300
}
}
@@ -63,7 +81,7 @@ Pane {
// }
Loader {
- Layout.fillHeight: true
+ // Layout.fillHeight: true
Layout.alignment: Qt.AlignRight | Qt.AlignTop
sourceComponent: control
diff --git a/qml/common/OptionGroup.qml b/qml/common/OptionGroup.qml
new file mode 100644
index 0000000..ec17053
--- /dev/null
+++ b/qml/common/OptionGroup.qml
@@ -0,0 +1,67 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+FocusScope {
+ id: root
+ required property string label
+
+ implicitHeight: theLabel.implicitHeight + thePane.implicitHeight
+ implicitWidth: theLabel.implicitWidth + thePane.implicitWidth
+
+ default property alias content: stuffCol.children
+
+ Text {
+ id: theLabel
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ text: root.label
+ font.pixelSize: 15
+ font.family: Constants.regularFontFamily
+ font.weight: Font.DemiBold
+ Layout.bottomMargin: 4
+ color: ColorPalette.neutral400
+ }
+
+ Pane {
+ id: thePane
+ anchors.top: theLabel.bottom
+ anchors.topMargin: 8
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+
+ topPadding: stuffCol.spacing
+ bottomPadding: topPadding * 3
+
+ background: Rectangle {
+ radius: 8
+ color: ColorPalette.neutral800
+ }
+
+ contentItem: ColumnLayout {
+ id: stuffCol
+
+ // ToggleOption {
+ // Layout.fillWidth: true
+ // label: "Simulate LCD ghosting"
+ // // description: "Enables simulation of LCD ghosting effects by blending the current and previous frames."
+ //
+ // Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "gambatte_mix_frames") === "accurate"
+ // }
+ //
+ // onCheckedChanged: {
+ // if (checked) {
+ // emulator_config_manager.setOptionValueForPlatform(1, "gambatte_mix_frames", "accurate")
+ // } else {
+ // emulator_config_manager.setOptionValueForPlatform(1, "gambatte_mix_frames", "disabled")
+ // }
+ // }
+ //
+ // }
+ }
+ }
+}
+
diff --git a/qml/common/RadioButtonGroup.qml b/qml/common/RadioButtonGroup.qml
new file mode 100644
index 0000000..ece5d26
--- /dev/null
+++ b/qml/common/RadioButtonGroup.qml
@@ -0,0 +1,113 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+FocusScope {
+ id: root
+
+ height: col.height
+
+ property string label
+ property string description
+
+ property string currentValue
+ required property var model
+
+ ColumnLayout {
+ id: col
+ width: root.width
+ // anchors.fill: parent
+
+ Text {
+ Layout.fillWidth: true
+ text: root.label
+ color: ColorPalette.neutral100
+ font.pixelSize: 15
+ Layout.alignment: Qt.AlignLeft
+ font.family: Constants.regularFontFamily
+ font.weight: Font.DemiBold
+ verticalAlignment: Text.AlignVCenter
+ }
+ // Text {
+ // Layout.fillWidth: true
+ // // visible: root.description !== ""
+ // text: root.description
+ // font.pixelSize: 13
+ // Layout.alignment: Qt.AlignLeft
+ // font.family: Constants.regularFontFamily
+ // // font.weight: Font.
+ // wrapMode: Text.WordWrap
+ // color: ColorPalette.neutral300
+ // }
+
+ Repeater {
+ model: root.model
+ delegate: RadioButton {
+ id: control
+
+ required property var model
+ required property var index
+
+ Layout.fillWidth: true
+ Layout.preferredHeight: 60
+ // checked: true
+ // Layout.fillWidth: true
+ // Layout.preferredHeight: 60
+
+ background: Rectangle {
+ radius: 8
+ color: ColorPalette.neutral800
+ }
+
+ indicator: Rectangle {
+ implicitWidth: 26
+ implicitHeight: 26
+ x: control.leftPadding
+ y: parent.height / 2 - height / 2
+ radius: 13
+ border.color: control.down ? "#17a81a" : "#21be2b"
+
+ Rectangle {
+ width: 14
+ height: 14
+ x: 6
+ y: 6
+ radius: 7
+ color: control.down ? "#17a81a" : "#21be2b"
+ visible: control.checked
+ }
+ }
+
+ contentItem: ColumnLayout {
+ Text {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ leftPadding: control.indicator.width + control.spacing
+ text: model.label
+ color: ColorPalette.neutral100
+ font.pixelSize: 15
+ Layout.alignment: Qt.AlignLeft
+ font.family: Constants.regularFontFamily
+ font.weight: Font.DemiBold
+ verticalAlignment: Text.AlignVCenter
+ }
+ Text {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ // visible: root.description !== ""
+ text: model.description
+ leftPadding: control.indicator.width + control.spacing
+ font.pixelSize: 13
+ Layout.alignment: Qt.AlignLeft
+ font.family: Constants.regularFontFamily
+ // font.weight: Font.
+ wrapMode: Text.WordWrap
+ color: ColorPalette.neutral300
+ }
+ }
+ }
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/qml/common/RightClickMenu.qml b/qml/common/RightClickMenu.qml
index 15c70cd..fd7e7cf 100644
--- a/qml/common/RightClickMenu.qml
+++ b/qml/common/RightClickMenu.qml
@@ -1,7 +1,5 @@
import QtQuick
import QtQuick.Controls
-import QtQuick.Layouts
-import QtQuick.Effects
Menu {
diff --git a/qml/common/SliderOption.qml b/qml/common/SliderOption.qml
index 7678ac1..d27b1df 100644
--- a/qml/common/SliderOption.qml
+++ b/qml/common/SliderOption.qml
@@ -2,10 +2,13 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
-
Option {
id: root
control: Slider {
-
+ from: 0
+ to: 50
+ stepSize: 5
+ value: 0
+ snapMode: Slider.SnapAlways
}
}
\ No newline at end of file
diff --git a/qml/common/ToggleOption.qml b/qml/common/ToggleOption.qml
index 18b7b8f..92eb15a 100644
--- a/qml/common/ToggleOption.qml
+++ b/qml/common/ToggleOption.qml
@@ -8,9 +8,7 @@ Option {
property bool checked: false
control: Switch {
- id: control
- Layout.fillHeight: true
- Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+ id: theControl
checked: root.checked
onCheckedChanged: {
root.checked = checked
@@ -19,11 +17,11 @@ Option {
indicator: Rectangle {
implicitWidth: 50
implicitHeight: 28
- x: control.leftPadding
+ x: theControl.leftPadding
y: parent.height / 2 - height / 2
radius: height / 2
- color: control.checked ? "#17a81a" : "#ffffff"
- border.color: control.checked ? "#17a81a" : "#cccccc"
+ color: theControl.checked ? "#17a81a" : "#ffffff"
+ border.color: theControl.checked ? "#17a81a" : "#cccccc"
Behavior on color {
ColorAnimation {
@@ -33,7 +31,7 @@ Option {
}
Rectangle {
- x: control.checked ? parent.width - width : 0
+ x: theControl.checked ? parent.width - width : 0
y: (parent.height - height) / 2
Behavior on x {
@@ -46,8 +44,8 @@ Option {
width: 26
height: 26
radius: height / 2
- color: control.down ? "#cccccc" : "#ffffff"
- border.color: control.checked ? (control.down ? "#17a81a" : "#21be2b") : "#999999"
+ color: theControl.down ? "#cccccc" : "#ffffff"
+ border.color: theControl.checked ? (theControl.down ? "#17a81a" : "#21be2b") : "#999999"
}
}
diff --git a/qml/controllers/ControllersPage.qml.bak b/qml/controllers/ControllersPage.qml.bak
deleted file mode 100644
index c2b64a4..0000000
--- a/qml/controllers/ControllersPage.qml.bak
+++ /dev/null
@@ -1,386 +0,0 @@
-import QtQuick
-import QtQuick.Controls
-import QtQuick.Layouts
-
-Pane {
- id: root
-
- background: Rectangle {
- color: "transparent"
- }
-
- ListView {
- id: playerList
- width: 400
- height: 400
- anchors.centerIn: parent
- spacing: 6
-
- model: controller_model
- delegate: RowLayout {
- implicitWidth: 300
- implicitHeight: 48
-
- Text {
- text: "P" + (model.player_index + 1)
- font.pointSize: 11
- font.family: Constants.semiboldFontFamily
- color: "#b3b3b3"
- Layout.fillWidth: true
- Layout.fillHeight: true
- Layout.horizontalStretchFactor: 1
- horizontalAlignment: Text.AlignLeft
- verticalAlignment: Text.AlignVCenter
- }
-
- ContentPane {
- Text {
- text: model.model_name
- anchors.fill: parent
- font.pointSize: 11
- font.family: Constants.semiboldFontFamily
- color: "white"
- horizontalAlignment: Text.AlignLeft
- verticalAlignment: Text.AlignVCenter
-
- }
- Layout.fillWidth: true
- Layout.fillHeight: true
- Layout.horizontalStretchFactor: 3
- }
- }
-
-
- // ContentPane {
- // Text {
- // text: controllerName
- // anchors.fill: parent
- // font.pointSize: 12
- // font.family: Constants.strongFontFamily
- // color: "white"
- // horizontalAlignment: Text.AlignLeft
- // verticalAlignment: Text.AlignVCenter
- //
- // }
- // width: parent.width
- // height: 48
- // }
- }
-
- // ColumnLayout {
- // id: players
- // anchors.centerIn: parent
- // spacing: 8
- //
- // RowLayout {
- // Layout.preferredWidth: 300
- // Layout.preferredHeight: 48
- //
- // Text {
- // text: "P1"
- // font.pointSize: 11
- // font.family: Constants.strongFontFamily
- // color: "#b3b3b3"
- // Layout.fillWidth: true
- // Layout.fillHeight: true
- // Layout.horizontalStretchFactor: 1
- // horizontalAlignment: Text.AlignLeft
- // verticalAlignment: Text.AlignVCenter
- // }
- //
- // ContentPane {
- // Text {
- // text: "Xbox One Controller for Windows"
- // anchors.fill: parent
- // font.pointSize: 12
- // font.family: Constants.strongFontFamily
- // color: "#b3b3b3"
- // horizontalAlignment: Text.AlignLeft
- // verticalAlignment: Text.AlignVCenter
- //
- // }
- // Layout.fillWidth: true
- // Layout.fillHeight: true
- // Layout.horizontalStretchFactor: 3
- // }
- // }
-
- // ContentPane {
- // id: playerOne
- // Layout.preferredWidth: 300
- // Layout.preferredHeight: 48
- //
- // Text {
- // text: "Player 1"
- // font.pointSize: 12
- // font.family: Constants.strongFontFamily
- // color: "#b3b3b3"
- // }
- // }
- //
- // ContentPane {
- // id: playerTwo
- // Layout.preferredWidth: 300
- // Layout.preferredHeight: 48
- //
- // Text {
- // text: "Player 2"
- // font.pointSize: 12
- // font.family: Constants.strongFontFamily
- // color: "#b3b3b3"
- // }
- // }
- //
- // ContentPane {
- // id: playerThree
- // Layout.preferredWidth: 300
- // Layout.preferredHeight: 48
- //
- // Text {
- // text: "Player 3"
- // font.pointSize: 12
- // font.family: Constants.strongFontFamily
- // color: "#b3b3b3"
- // }
- // }
- //
- // ContentPane {
- // id: playerFour
- // Layout.preferredWidth: 300
- // Layout.preferredHeight: 48
- //
- // Text {
- // text: "Player 4"
- // font.pointSize: 12
- // font.family: Constants.strongFontFamily
- // color: "#b3b3b3"
- // }
- // }
-
- // Thing {
- // Layout.preferredWidth: parent.width
- // Layout.minimumHeight: 48
- // Layout.fillHeight: true
- //
- // Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
- // }
- //
- // Thing {
- // Layout.preferredWidth: parent.width
- // Layout.minimumHeight: 48
- // Layout.fillHeight: true
- //
- // Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
- // }
- //
- // Thing {
- // Layout.preferredWidth: parent.width
- // Layout.minimumHeight: 48
- // Layout.fillHeight: true
- //
- // Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
- // }
- //
- // Thing {
- // Layout.preferredWidth: parent.width
- // Layout.minimumHeight: 48
- // Layout.fillHeight: true
- //
- // Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
- // }
-
- // }
-
-
- // Item {
- // GridView {
- // id: root
- // width: parent.width
- // height: parent.height
- // cellWidth: parent.width
- // cellHeight: parent.height / 4
- //
- // displaced: Transition {
- // NumberAnimation {
- // properties: "x,y"
- // easing.type: Easing.OutQuad
- // }
- // }
- //
- // // model: controller_model
- // // delegate: Rectangle {
- // // width: 300
- // // height: 72
- // // color: "black"
- // // radius: 12
- // //
- // // Text {
- // // anchors.centerIn: parent
- // // color: "white"
- // // text: playerIndex + ": " + controllerName
- // // }
- // //
- // // DragHandler {
- // // id: dragHandler
- // // }
- // // }
- //
- // model: DelegateModel {
- // id: visualModel
- // model: controller_model
- //
- // component Thing: Rectangle {
- // id: icon
- // required property Item dragParent
- // required property string controllerName
- // required property int playerIndex
- //
- // property int visualIndex: 0
- // width: 300
- // height: 72
- // color: "black"
- //
- // anchors {
- // horizontalCenter: parent.horizontalCenter
- // verticalCenter: parent.verticalCenter
- // }
- // radius: 12
- //
- // Text {
- // anchors.centerIn: parent
- // color: "white"
- // text: controllerName
- // }
- //
- // DragHandler {
- // id: dragHandler
- // }
- //
- // HoverHandler {
- // cursorShape: icon.state === "Dragging" ? Qt.ClosedHandCursor : Qt.OpenHandCursor
- // }
- //
- // Drag.active: dragHandler.active
- // Drag.source: icon
- // Drag.hotSpot.x: 36
- // Drag.hotSpot.y: 36
- //
- // states: [
- // State {
- // name: "NotDragging"
- // when: !dragHandler.active
- // },
- // State {
- // name: "Dragging"
- // when: dragHandler.active
- // ParentChange {
- // target: icon
- // parent: icon.dragParent
- // }
- //
- // AnchorChanges {
- // target: icon
- // anchors {
- // horizontalCenter: undefined
- // verticalCenter: undefined
- // }
- // }
- // }
- // ]
- //
- // transitions: [
- // Transition {
- // from: "Dragging"
- // to: "NotDragging"
- // ScriptAction {
- // script: {
- // var newOrder = {};
- // for (var i = 0; i < visualModel.items.count; i++) {
- // newOrder[i] = visualModel.items.get(i).model.playerIndex
- // // console.log("playerIndex: " + visualModel.items.get(i).model.playerIndex)
- // }
- // // for (const key in visualModel.items) {
- // // if (myObject.hasOwnProperty(key)) {
- // // newOrder[visualModel.items[key].model.visualIndex] = visualModel.items[key].model.playerIndex;
- // // }
- // // }
- //
- // controller_model.updateControllerOrder(newOrder)
- // // for (var i = 0; i < visualModel.items.count; i++) {
- // // console.log("playerIndex: " + visualModel.items.get(i).model.playerIndex)
- // // }
- // }
- //
- // }
- // }
- // ]
- // }
- //
- // delegate: DropArea {
- // id: delegateRoot
- // required property string controllerName
- // required property int playerIndex
- //
- // width: 300
- // height: 80
- //
- // onEntered: function (drag) {
- // visualModel.items.move(drag.source.visualIndex, icon2.visualIndex)
- // controller_model.swap(drag.source.visualIndex, icon2.visualIndex)
- // }
- //
- // property int visualIndex: DelegateModel.itemsIndex
- //
- // Thing {
- // id: icon2
- // dragParent: root
- // visualIndex: delegateRoot.visualIndex
- // controllerName: delegateRoot.controllerName
- // playerIndex: delegateRoot.playerIndex
- // }
- // }
- // }
- // }
- // }
-
- component Thing: Pane {
- id: con
-
- background: Rectangle {
- color: "white"
- opacity: conHover.hovered ? 0.2 : 0.1
- Behavior on opacity {
- NumberAnimation {
- duration: 200
- easing.type: Easing.InOutQuad
- }
- }
- radius: 8
- }
- padding: 16
-
- Rectangle {
- id: conIcon
- width: con.height - (padding * 2)
- height: con.height - (padding * 2)
- anchors.verticalCenter: parent.verticalCenter
- radius: 8
- color: "white"
- opacity: 0.6
- }
-
- Text {
- anchors.left: conIcon.right
- anchors.leftMargin: 16
- anchors.top: parent.top
- text: "Player 1"
- font.pointSize: 12
- font.family: Constants.strongFontFamily
- color: "#b3b3b3"
- }
-
- HoverHandler {
- id: conHover
- }
- }
-}
\ No newline at end of file
diff --git a/qml/discover/HackPage.qml b/qml/discover/HackPage.qml
deleted file mode 100644
index 277f850..0000000
--- a/qml/discover/HackPage.qml
+++ /dev/null
@@ -1,197 +0,0 @@
-import QtQuick
-import QtQuick.Controls
-import QtQuick.Layouts
-import QtQml.Models
-
-
-Pane {
- id: root
-
- background: Item {
- }
-
- ContentPane {
- id: content
- anchors.fill: parent
-
- Pane {
- id: rightSection
-
- background: Item {
- }
-
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- anchors.right: parent.right
- width: parent.width * 0.3
-
- ColumnLayout {
- anchors.centerIn: parent
- spacing: 8
-
- Button {
- id: addToLibraryButton
- text: "Add to Library"
- Layout.preferredWidth: 200
- Layout.preferredHeight: 60
- Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
- }
- Item {
- Layout.fillWidth: true
- Layout.preferredHeight: 8
- }
- Text {
- text: "You need Pokémon Fire Red in your Library to play this game"
- font.family: Constants.regularFontFamily
- color: "white"
- wrapMode: Text.WordWrap
- font.pointSize: 10
- Layout.preferredWidth: 300
- Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
- horizontalAlignment: Text.AlignHCenter
- }
-
- Item {
- Layout.fillWidth: true
- Layout.fillHeight: true
- }
- }
- }
-
- Pane {
- id: titleBar
-
- background: Item {
- }
-
- anchors.top: parent.top
- anchors.right: rightSection.left
- anchors.left: parent.left
-
- ColumnLayout {
- anchors.fill: parent
- spacing: 8
-
- Text {
- text: "Pokémon Radical Red"
- horizontalAlignment: Text.AlignLeft
- verticalAlignment: Text.AlignVCenter
- font.pointSize: 20
- font.family: Constants.semiboldFontFamily
- color: "white"
- }
-
- Text {
- text: "By soupercell"
- horizontalAlignment: Text.AlignLeft
- verticalAlignment: Text.AlignVCenter
- font.pointSize: 11
- font.family: Constants.regularFontFamily
- color: "white"
- }
-
-
- }
- }
-
- Pane {
- id: leftSection
-
- background: Item {
- }
- anchors.top: titleBar.bottom
- anchors.bottom: parent.bottom
- anchors.left: parent.left
- anchors.right: rightSection.left
-
- Flickable {
- anchors.fill: parent
- contentWidth: leftSection.width
- contentHeight: 1000
- boundsBehavior: Flickable.StopAtBounds
- flickableDirection: Flickable.VerticalFlick
- clip: true
-
- ColumnLayout {
- id: column
- anchors.fill: parent
- spacing: 16
-
- ListView {
- id: screenshots
- Layout.alignment: Qt.AlignLeft | Qt.AlignTop
- Layout.preferredHeight: 240
- Layout.fillWidth: true
- clip: true
- orientation: ListView.Horizontal
- model: ListModel {
- ListElement {
- source: "file:///Users/alexs/git/firelight/build/prr1.png"
- }
- ListElement {
- source: "file:///Users/alexs/git/firelight/build/prr2.png"
- }
- ListElement {
- source: "file:///Users/alexs/git/firelight/build/prr3.jpg"
- }
- ListElement {
- source: "file:///Users/alexs/git/firelight/build/prr4.jpg"
- }
- }
- spacing: 8
- delegate: Image {
- source: model.source
- fillMode: Image.Stretch
- width: parent.height * 1.5
- height: parent.height
- }
- }
-
- Text {
- Layout.fillWidth: true
- Layout.alignment: Qt.AlignLeft | Qt.AlignTop
- text: "Pokémon Radical Red is an enhancement hack of Pokémon Fire Red.
- This is a difficulty hack, with massive additional features added to help you navigate through this game.
- This hack utilises the Complete Fire Red Upgrade Engine and Dynamic Pokemon Expansion built
- by Skeli789, Ghoulslash, and others. It's responsible for most of the significant features
- in the hack.
- List of features (Most of them provided by CFRU and DPE):
-
- - Much higher difficulty, with optional modes to add or mitigate difficulty
- - Built-in Randomizer options (Pokémon, Abilities and Learnsets)
- - Physical/Special split + Fairy Typing
- - All Pokémon up to Gen 9 obtainable (with some exceptions)
- - Most Moves up to Gen 9
- - Updated Pokémon sprites
- - Mega Evolutions & Z-Moves
- - Most Abilities up to Gen 9
- - All important battle items (with some exceptions)
- - Wish Piece Raid Battles (with Dynamax)
- - Mystery Gifts
- - Reusable TMs
- - Expanded TM list
- - Additional move tutors
- - EV Training Gear and NPCs
- - Ability popups during battle
- - Party Exp Share (can be disabled)
- - Hidden Abilities
- - Day, Dusk and Night cycle (syncs with RTC)
- - DexNav, which allows you to search for Pokémon with hidden abilities and more
- - Even faster turbo speed on bike and while surfing
- - Abilities like Magma Armor, Static, or Flash Fire have overworld effects like in recent generations
- - Destiny Knot, Everstone have updated breeding mechanics
- - Lots of Quality of Life changes
- - ... and more!
-
"
- wrapMode: Text.WordWrap
- font.pointSize: 12
- font.family: Constants.regularFontFamily
- color: "white"
- }
- }
- }
-
-
- }
- }
-}
\ No newline at end of file
diff --git a/qml/discover/HackPage2.qml b/qml/discover/HackPage2.qml
deleted file mode 100644
index f439052..0000000
--- a/qml/discover/HackPage2.qml
+++ /dev/null
@@ -1,336 +0,0 @@
-import QtQuick
-import QtQuick.Controls
-import QtQuick.Layouts
-import QtQml.Models
-
-
-Item {
- id: root
-
- ContentPane {
- id: content
- anchors.fill: parent
-
- Pane {
- id: banner
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.right: parent.right
- height: 200
-
- background: Rectangle {
- color: "red"
- }
-
- ColumnLayout {
- anchors.fill: parent
- spacing: 8
-
- Item {
- Layout.fillHeight: true
- Layout.fillWidth: true
- }
-
- Text {
- text: "Pokémon Radical Red"
- horizontalAlignment: Text.AlignLeft
- verticalAlignment: Text.AlignVCenter
- font.pointSize: 20
- font.family: Constants.semiboldFontFamily
- color: "white"
- }
-
- Text {
- text: "By soupercell"
- horizontalAlignment: Text.AlignLeft
- verticalAlignment: Text.AlignVCenter
- font.pointSize: 11
- font.family: Constants.regularFontFamily
- color: "white"
- }
-
-
- }
- }
-
- Pane {
- id: body
- anchors.top: banner.bottom
- anchors.bottom: parent.bottom
- anchors.horizontalCenter: parent.horizontalCenter
- width: 800
-
- background: Rectangle {
- color: "blue"
- }
-
- Flickable {
- anchors.fill: parent
- contentWidth: parent.width
- contentHeight: 1000
- boundsBehavior: Flickable.StopAtBounds
- flickableDirection: Flickable.VerticalFlick
- clip: true
-
- ColumnLayout {
- id: column
- anchors.fill: parent
- spacing: 16
-
- ListView {
- id: screenshots
- Layout.alignment: Qt.AlignLeft | Qt.AlignTop
- Layout.preferredHeight: 140
- Layout.fillWidth: true
- clip: true
- orientation: ListView.Horizontal
- model: ListModel {
- ListElement {
- source: "file:///Users/alexs/git/firelight/build/prr1.png"
- }
- ListElement {
- source: "file:///Users/alexs/git/firelight/build/prr2.png"
- }
- ListElement {
- source: "file:///Users/alexs/git/firelight/build/prr3.jpg"
- }
- ListElement {
- source: "file:///Users/alexs/git/firelight/build/prr4.jpg"
- }
- }
- spacing: 8
- delegate: Image {
- source: model.source
- fillMode: Image.Stretch
- width: parent.height * 1.5
- height: parent.height
- }
- }
-
- Text {
- Layout.fillWidth: true
- Layout.alignment: Qt.AlignLeft | Qt.AlignTop
- text: "Pokémon Radical Red is an enhancement hack of Pokémon Fire Red.
- This is a difficulty hack, with massive additional features added to help you navigate through this game.
- This hack utilises the Complete Fire Red Upgrade Engine and Dynamic Pokemon Expansion built
- by Skeli789, Ghoulslash, and others. It's responsible for most of the significant features
- in the hack.
- List of features (Most of them provided by CFRU and DPE):
-
- - Much higher difficulty, with optional modes to add or mitigate difficulty
- - Built-in Randomizer options (Pokémon, Abilities and Learnsets)
- - Physical/Special split + Fairy Typing
- - All Pokémon up to Gen 9 obtainable (with some exceptions)
- - Most Moves up to Gen 9
- - Updated Pokémon sprites
- - Mega Evolutions & Z-Moves
- - Most Abilities up to Gen 9
- - All important battle items (with some exceptions)
- - Wish Piece Raid Battles (with Dynamax)
- - Mystery Gifts
- - Reusable TMs
- - Expanded TM list
- - Additional move tutors
- - EV Training Gear and NPCs
- - Ability popups during battle
- - Party Exp Share (can be disabled)
- - Hidden Abilities
- - Day, Dusk and Night cycle (syncs with RTC)
- - DexNav, which allows you to search for Pokémon with hidden abilities and more
- - Even faster turbo speed on bike and while surfing
- - Abilities like Magma Armor, Static, or Flash Fire have overworld effects like in recent generations
- - Destiny Knot, Everstone have updated breeding mechanics
- - Lots of Quality of Life changes
- - ... and more!
-
"
- wrapMode: Text.WordWrap
- font.pointSize: 12
- font.family: Constants.regularFontFamily
- color: "white"
- }
- }
- }
- }
-
-
- // Pane {
- // id: rightSection
- //
- // background: Item {
- // }
- //
- // anchors.top: parent.top
- // anchors.bottom: parent.bottom
- // anchors.right: parent.right
- // width: parent.width * 0.3
- //
- // ColumnLayout {
- // anchors.centerIn: parent
- // spacing: 8
- //
- // Button {
- // id: addToLibraryButton
- // text: "Add to Library"
- // Layout.preferredWidth: 200
- // Layout.preferredHeight: 60
- // Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
- // }
- // Item {
- // Layout.fillWidth: true
- // Layout.preferredHeight: 8
- // }
- // Text {
- // text: "You need Pokémon Fire Red in your Library to play this game"
- // font.family: Constants.regularFontFamily
- // color: "white"
- // wrapMode: Text.WordWrap
- // font.pointSize: 10
- // Layout.preferredWidth: 300
- // Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
- // horizontalAlignment: Text.AlignHCenter
- // }
- //
- // Item {
- // Layout.fillWidth: true
- // Layout.fillHeight: true
- // }
- // }
- // }
- //
- // Pane {
- // id: titleBar
- //
- // background: Item {
- // }
- //
- // anchors.top: parent.top
- // anchors.right: rightSection.left
- // anchors.left: parent.left
- //
- // ColumnLayout {
- // anchors.fill: parent
- // spacing: 8
- //
- // Text {
- // text: "Pokémon Radical Red"
- // horizontalAlignment: Text.AlignLeft
- // verticalAlignment: Text.AlignVCenter
- // font.pointSize: 20
- // font.family: Constants.semiboldFontFamily
- // color: "white"
- // }
- //
- // Text {
- // text: "By soupercell"
- // horizontalAlignment: Text.AlignLeft
- // verticalAlignment: Text.AlignVCenter
- // font.pointSize: 11
- // font.family: Constants.regularFontFamily
- // color: "white"
- // }
- //
- //
- // }
- // }
- //
- // Pane {
- // id: leftSection
- //
- // background: Item {
- // }
- // anchors.top: titleBar.bottom
- // anchors.bottom: parent.bottom
- // anchors.left: parent.left
- // anchors.right: rightSection.left
- //
- // Flickable {
- // anchors.fill: parent
- // contentWidth: leftSection.width
- // contentHeight: 1000
- // boundsBehavior: Flickable.StopAtBounds
- // flickableDirection: Flickable.VerticalFlick
- // clip: true
- //
- // ColumnLayout {
- // id: column
- // anchors.fill: parent
- // spacing: 16
- //
- // ListView {
- // id: screenshots
- // Layout.alignment: Qt.AlignLeft | Qt.AlignTop
- // Layout.preferredHeight: 240
- // Layout.fillWidth: true
- // clip: true
- // orientation: ListView.Horizontal
- // model: ListModel {
- // ListElement {
- // source: "file:///Users/alexs/git/firelight/build/prr1.png"
- // }
- // ListElement {
- // source: "file:///Users/alexs/git/firelight/build/prr2.png"
- // }
- // ListElement {
- // source: "file:///Users/alexs/git/firelight/build/prr3.jpg"
- // }
- // ListElement {
- // source: "file:///Users/alexs/git/firelight/build/prr4.jpg"
- // }
- // }
- // spacing: 8
- // delegate: Image {
- // source: model.source
- // fillMode: Image.Stretch
- // width: parent.height * 1.5
- // height: parent.height
- // }
- // }
- //
- // Text {
- // Layout.fillWidth: true
- // Layout.alignment: Qt.AlignLeft | Qt.AlignTop
- // text: "Pokémon Radical Red is an enhancement hack of Pokémon Fire Red.
- // This is a difficulty hack, with massive additional features added to help you navigate through this game.
- // This hack utilises the Complete Fire Red Upgrade Engine and Dynamic Pokemon Expansion built
- // by Skeli789, Ghoulslash, and others. It's responsible for most of the significant features
- // in the hack.
- // List of features (Most of them provided by CFRU and DPE):
- //
- // - Much higher difficulty, with optional modes to add or mitigate difficulty
- // - Built-in Randomizer options (Pokémon, Abilities and Learnsets)
- // - Physical/Special split + Fairy Typing
- // - All Pokémon up to Gen 9 obtainable (with some exceptions)
- // - Most Moves up to Gen 9
- // - Updated Pokémon sprites
- // - Mega Evolutions & Z-Moves
- // - Most Abilities up to Gen 9
- // - All important battle items (with some exceptions)
- // - Wish Piece Raid Battles (with Dynamax)
- // - Mystery Gifts
- // - Reusable TMs
- // - Expanded TM list
- // - Additional move tutors
- // - EV Training Gear and NPCs
- // - Ability popups during battle
- // - Party Exp Share (can be disabled)
- // - Hidden Abilities
- // - Day, Dusk and Night cycle (syncs with RTC)
- // - DexNav, which allows you to search for Pokémon with hidden abilities and more
- // - Even faster turbo speed on bike and while surfing
- // - Abilities like Magma Armor, Static, or Flash Fire have overworld effects like in recent generations
- // - Destiny Knot, Everstone have updated breeding mechanics
- // - Lots of Quality of Life changes
- // - ... and more!
- //
"
- // wrapMode: Text.WordWrap
- // font.pointSize: 12
- // font.family: Constants.regularFontFamily
- // color: "white"
- // }
- // }
- // }
- //
- //
- // }
- }
-}
\ No newline at end of file
diff --git a/qml/home/HomeContentPane.qml b/qml/home/HomeContentPane.qml
new file mode 100644
index 0000000..1c19ddd
--- /dev/null
+++ b/qml/home/HomeContentPane.qml
@@ -0,0 +1,92 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Dialogs
+import QtQuick.Window
+import QtQuick.Layouts 1.0
+import QtQuick.Effects
+import Firelight 1.0
+
+
+StackView {
+ objectName: "Home Content Stack View"
+ id: stackview
+ anchors.fill: parent
+
+ property alias currentPageName: stackview.topLevelName
+
+ function goTo(page) {
+ stackview.replace(null, page)
+ }
+
+ property string topLevelName: ""
+
+ onCurrentItemChanged: {
+ if (currentItem) {
+ let top = stackview.find(function (item, index) {
+ return item.topLevel === true
+ })
+
+ stackview.topLevelName = top ? top.topLevelName : ""
+ }
+ }
+
+ // Pane {
+ // width: 48
+ // height: 48
+ //
+ // z: 2
+ //
+ // background: Item {
+ // }
+ //
+ // Button {
+ // id: melol
+ // anchors.left: parent.left
+ // anchors.verticalCenter: parent.verticalCenter
+ // // horizontalPadding: 12
+ // // verticalPadding: 8
+ //
+ // enabled: stackview.depth > 1
+ //
+ // hoverEnabled: false
+ //
+ // HoverHandler {
+ // id: myHover
+ // cursorShape: melol.enabled ? Qt.PointingHandCursor : Qt.ForbiddenCursor
+ // }
+ //
+ // background: Rectangle {
+ // color: enabled ? myHover.hovered ? "#4e535b" : "#3e434b" : "#3e434b"
+ // radius: height / 2
+ // }
+ //
+ // contentItem: Text {
+ // text: "\ue5c4"
+ // color: enabled ? "white" : "#7d848c"
+ // font.pointSize: 11
+ // font.family: Constants.symbolFontFamily
+ // horizontalAlignment: Text.AlignHCenter
+ // verticalAlignment: Text.AlignVCenter
+ // }
+ //
+ // onClicked: {
+ // stackview.pop()
+ // }
+ // }
+ // }
+
+ // initialItem: libraryPage
+
+ pushEnter: Transition {
+ }
+ pushExit: Transition {
+ }
+ popEnter: Transition {
+ }
+ popExit: Transition {
+ }
+ replaceEnter: Transition {
+ }
+ replaceExit: Transition {
+ }
+}
\ No newline at end of file
diff --git a/qml/library/EntrySummaryPage.qml b/qml/library/EntrySummaryPage.qml
new file mode 100644
index 0000000..6561308
--- /dev/null
+++ b/qml/library/EntrySummaryPage.qml
@@ -0,0 +1,54 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+ColumnLayout {
+ id: root
+ objectName: "Details Tab Content"
+
+ required property var entryData
+
+ Text {
+ Layout.fillWidth: true
+ text: qsTr("Content path")
+ color: "white"
+ font.pointSize: 12
+ font.family: Constants.regularFontFamily
+ font.weight: Font.DemiBold
+ }
+ Pane {
+ id: texxxxt
+ // Layout.fillWidth: true
+ padding: 4
+
+ background: Rectangle {
+ color: "black"
+ radius: 8
+ }
+
+ contentItem: TextInput {
+ padding: 4
+ text: root.entryData.content_path
+ font.family: Constants.regularFontFamily
+ font.pointSize: 12
+ color: "white"
+ verticalAlignment: Text.AlignVCenter
+ readOnly: true
+ }
+ }
+ Text {
+ text: "Active save slot: " + root.entryData.active_save_slot
+ color: "white"
+ font.family: Constants.regularFontFamily
+ font.pointSize: 10
+ }
+ Text {
+ text: "Added to library: " + root.entryData.created_at
+ color: "white"
+ font.family: Constants.regularFontFamily
+ font.pointSize: 10
+ }
+ Item {
+ Layout.fillHeight: true
+ }
+}
\ No newline at end of file
diff --git a/qml/library/GameDetailsPage.qml b/qml/library/GameDetailsPage.qml
deleted file mode 100644
index cc3f087..0000000
--- a/qml/library/GameDetailsPage.qml
+++ /dev/null
@@ -1,755 +0,0 @@
-import QtQuick
-import QtQuick.Controls
-import QtQuick.Layouts
-import QtQuick.Effects
-import QtCharts 2.7
-
-Item {
- id: root
-
- signal playPressed()
-
- required property int entryId
- property int tabWidth: 150
- property var achievements: null
- property var achievementsSummary: {
- "NumPossibleAchievements": 0,
- "PossibleScore": 0,
- "NumAchieved": 0,
- "ScoreAchieved": 0,
- "NumAchievedHardcore": 0,
- "ScoreAchievedHardcore": 0
-
- }
- property var entryData: {}
-
- Component.onCompleted: {
- entryData = library_database.getLibraryEntryJson(entryId)
-
- root.achievements = achievement_manager.getAchievementsModelForGameId(entryData.game_id)
- achievement_manager.getAchievementsOverview(entryData.game_id)
- }
-
- Connections {
- target: achievement_manager
-
- function onAchievementSummaryAvailable(json) {
- root.achievementsSummary = json
- }
-
- // function onAchievementListAvailable(model) {
- // root.achievements = model
- // }
- }
-
- // Rectangle {
- // width: parent.width + 24
- // height: topRow.height + 12
- // x: -12
- // y: -12
- //
- // color: "#101114"
- // }
-
- // Image {
- // id: headerBanner
- // width: parent.width + 24
- // height: topRow.height
- // x: -12
- // y: -12
- //
- // source: "file:system/_img/smw_beachkoopa_slope.png"
- // fillMode: Image.Stretch
- //
- // layer.enabled: true
- // layer.effect: MultiEffect {
- // autoPaddingEnabled: false
- // source: headerBanner
- // anchors.fill: headerBanner
- // blurEnabled: true
- // blurMultiplier: 1.0
- // blurMax: 64
- // blur: 1
- // }
- //
- // Rectangle {
- // anchors.fill: parent
- // color: "black"
- // opacity: 0.3
- // }
- // }
-
- ColumnLayout {
- spacing: 0
- anchors.fill: parent
-
- RowLayout {
- id: topRow
- Layout.fillWidth: true
- Layout.topMargin: 12
- Layout.minimumHeight: 84
- Layout.maximumHeight: 84
- spacing: 0
-
- Text {
- Layout.alignment: Qt.AlignTop
- Layout.leftMargin: 48
- padding: 10
- text: root.entryData.display_name
- color: "white"
- font.pointSize: 22
- font.family: Constants.regularFontFamily
- font.weight: Font.DemiBold
- horizontalAlignment: Text.AlignLeft
- verticalAlignment: Text.AlignVCenter
- }
-
- Item {
- Layout.fillWidth: true
- Layout.fillHeight: true
- }
-
- Button {
- Layout.alignment: Qt.AlignRight | Qt.AlignTop
- Layout.preferredWidth: 140
- Layout.preferredHeight: 50
- Layout.rightMargin: 48
- background: Rectangle {
- color: parent.hovered ? "#b8b8b8" : "white"
- radius: 4
- }
- hoverEnabled: true
- contentItem: Text {
- text: qsTr("Play")
- color: Constants.colorTestBackground
- font.family: Constants.regularFontFamily
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- font.pointSize: 12
- }
- onClicked: {
- root.playPressed()
- }
- }
- }
- RowLayout {
- Layout.fillWidth: true
- Layout.fillHeight: true
-
- spacing: 0
-
- Item {
- Layout.fillWidth: true
- Layout.fillHeight: true
- Layout.horizontalStretchFactor: 1
- }
-
- ColumnLayout {
- id: theColumn
- Layout.fillWidth: true
- Layout.horizontalStretchFactor: 4
- Layout.minimumWidth: 700
- Layout.maximumWidth: 1200
- Layout.preferredWidth: parent.width * 3 / 4
- Layout.fillHeight: true
-
- TabBar {
- id: bar
- // Layout.topMargin: 16
- Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
- Layout.preferredHeight: 40
- currentIndex: -1
-
- onCurrentIndexChanged: function () {
- view.setCurrentIndex(currentIndex)
- }
-
- background: Rectangle {
- width: root.tabWidth
- visible: bar.contentChildren[bar.currentIndex].enabled
- height: 2
- radius: 1
- color: "white"
- x: root.tabWidth * bar.currentIndex
- y: bar.height
-
- Behavior on x {
- NumberAnimation {
- duration: 120
- easing.type: Easing.InOutQuad
- }
- }
- }
-
- TabButton {
- width: root.tabWidth
- contentItem: Text {
- text: "Details"
- color: parent.enabled ? "#ffffff" : "#666666"
- font.family: Constants.regularFontFamily
- font.pointSize: 11
- font.weight: parent.enabled && parent.checked ? Font.Bold : Font.Normal
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- }
-
- background: Item {
- }
-
- HoverHandler {
- cursorShape: Qt.PointingHandCursor
- }
- }
- TabButton {
- width: root.tabWidth
- contentItem: Text {
- text: "Achievements"
- color: parent.enabled ? "#ffffff" : "#666666"
- font.family: Constants.regularFontFamily
- font.pointSize: 11
- font.weight: parent.enabled && parent.checked ? Font.Bold : Font.Normal
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- }
-
- background: Item {
- }
-
- HoverHandler {
- cursorShape: Qt.PointingHandCursor
- }
- }
- TabButton {
- width: root.tabWidth
- contentItem: Text {
- text: "Activity"
- color: parent.enabled ? "#ffffff" : "#666666"
- font.family: Constants.regularFontFamily
- font.pointSize: 11
- font.weight: parent.enabled && parent.checked ? Font.Bold : Font.Normal
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- }
-
- background: Item {
- }
-
- HoverHandler {
- cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
- }
- }
- TabButton {
- width: root.tabWidth
- contentItem: Text {
- text: "Settings"
- color: parent.enabled ? "#ffffff" : "#666666"
- font.family: Constants.regularFontFamily
- font.pointSize: 11
- font.weight: parent.enabled && parent.checked ? Font.Bold : Font.Normal
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- }
-
- background: Item {
- }
-
- HoverHandler {
- cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
- }
- }
- }
- SwipeView {
- id: view
- Layout.fillWidth: true
- Layout.fillHeight: true
-
- currentIndex: 0
-
- clip: true
-
- onCurrentIndexChanged: function () {
- bar.setCurrentIndex(currentIndex)
- }
-
- // ChartView {
- // title: "Line Chart"
- // theme: ChartView.ChartThemeBrownSand
- // antialiasing: true
- //
- // BarCategoryAxis {
- // id: daysAxis
- // categories: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
- // }
- //
- // ValueAxis {
- // id: playtimeAxis
- // min: 0
- // max: 10
- // titleText: "Playtime (hours)"
- // }
- //
- // BarSeries {
- // id: barSeries
- // axisX: daysAxis
- // axisY: playtimeAxis
- //
- // BarSet {
- // label: "Playtime"
- // // Sample data
- // values: [3, 5, 2, 7, 6, 1, 4]
- // }
- // }
- //
- // Component.onCompleted: {
- // barSeries.append("Playtime", [3, 5, 2, 7, 6, 1, 4])
- // }
- // }
- ColumnLayout {
- Text {
- Layout.fillWidth: true
- text: qsTr("Content path")
- color: "white"
- font.pointSize: 12
- font.family: Constants.regularFontFamily
- font.weight: Font.DemiBold
- }
- Pane {
- id: texxxxt
- // Layout.fillWidth: true
- padding: 4
-
- background: Rectangle {
- color: "black"
- radius: 8
- }
-
- contentItem: TextInput {
- padding: 4
- text: root.entryData.content_path
- font.family: Constants.regularFontFamily
- font.pointSize: 12
- color: "white"
- verticalAlignment: Text.AlignVCenter
- readOnly: true
- }
- }
- Text {
- text: "Active save slot: " + root.entryData.active_save_slot
- color: "white"
- font.family: Constants.regularFontFamily
- font.pointSize: 10
- }
- Text {
- text: "Added to library: " + root.entryData.created_at
- color: "white"
- font.family: Constants.regularFontFamily
- font.pointSize: 10
- }
- Item {
- Layout.fillHeight: true
- }
- }
- Flickable {
- id: flickThing
- contentHeight: !achievement_manager.loggedIn ? thing.height : achievementsList.height
-
- property real scrollMultiplier: 8.0 // Adjust this multiplier for desired scroll speed
- property real maxScrollSpeed: 1000 // Maximum scroll speed
- property real smoothScrollSpeed: 0.1 // Adjust this for smoothness
-
- // Behavior on contentY {
- // NumberAnimation {
- // duration: 120
- // easing.type: Easing.InOutQuad
- // }
- // }
-
-
- MouseArea {
- anchors.fill: parent
- acceptedButtons: Qt.NoButton
- // preventStealing: true
- onWheel: function (wheel) {
- var scrollDelta;
- if (wheel.pixelDelta.y !== 0) {
- scrollDelta = wheel.pixelDelta.y;
- } else {
- scrollDelta = wheel.angleDelta.y / 8;
- }
-
- flickThing.contentY -= scrollDelta * flickThing.scrollMultiplier
- if (flickThing.contentY < 0) {
- flickThing.contentY = 0
- } else if (flickThing.contentY > flickThing.contentHeight - flickThing.height) {
- flickThing.contentY = flickThing.contentHeight - flickThing.height
- }
-
- //
- // // Dynamic scaling factor for fast scrolling
- // var scaledDelta = scrollDelta * flickThing.scrollMultiplier;
- // var absDelta = Math.abs(scrollDelta);
- //
- // // Increase scroll speed for larger deltas (fast scrolling)
- // if (absDelta > 10) {
- // scaledDelta *= (absDelta / 10);
- // scaledDelta = Math.sign(scrollDelta) * Math.min(flickThing.maxScrollSpeed, Math.abs(scaledDelta));
- // }
- //
- // flickThing.contentY = Math.max(0, Math.min(flickThing.contentY + scaledDelta, flickThing.contentHeight - flickThing.height));
- }
- }
- ColumnLayout {
- id: thing
- visible: !achievement_manager.loggedIn
- Item {
- Layout.fillWidth: true
- Layout.fillHeight: true
- }
- Text {
- text: "Log in blah blah"
- color: "white"
- font.family: Constants.regularFontFamily
- font.pointSize: 10
- Layout.alignment: Qt.AlignCenter
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- }
- Button {
- Layout.alignment: Qt.AlignCenter
- Layout.preferredWidth: 140
- Layout.preferredHeight: 50
- background: Rectangle {
- color: parent.hovered ? "#b8b8b8" : "white"
- radius: 4
- }
- hoverEnabled: true
- contentItem: Text {
- text: qsTr("Log in")
- color: Constants.colorTestBackground
- font.family: Constants.regularFontFamily
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- font.pointSize: 12
- }
- onClicked: {
- Router.navigateTo("settings/achievements")
- }
- }
- Item {
- Layout.fillWidth: true
- Layout.fillHeight: true
- }
- }
-
- Column {
- id: achievementsList
- visible: achievement_manager.loggedIn && root.achievementsSummary.NumPossibleAchievements > 0
- width: parent.width
- spacing: 24
-
- Item {
- width: parent.width
- height: 24
- }
-
- // Pane {
- // width: 540
- // height: 106
- // padding: 8
- //
- // background: Rectangle {
- // color: "#292929"
- // radius: 8
- // }
- //
- // contentItem: Item {
- // Image {
- // id: gameIcon
- // width: parent.height
- // height: parent.height
- // fillMode: Image.PreserveAspectFit
- // source: "https://media.retroachievements.org/Images/040155.png"
- // }
- // // Rectangle {
- // // id: gameIcon
- // // color: "red"
- // // width: parent.height
- // // height: parent.height
- // // }
- // ColumnLayout {
- // anchors.leftMargin: 12
- // anchors.left: gameIcon.right
- // anchors.top: parent.top
- // anchors.bottom: parent.bottom
- // anchors.right: parent.right
- // spacing: 4
- //
- // RowLayout {
- // Layout.fillWidth: true
- // Layout.fillHeight: true
- // Layout.verticalStretchFactor: 3
- // spacing: 0
- // Text {
- // Layout.fillHeight: true
- // text: root.entryData.display_name
- // font.family: Constants.regularFontFamily
- // font.pointSize: 12
- // font.weight: Font.DemiBold
- // verticalAlignment: Text.AlignVCenter
- // color: "white"
- // }
- // Item {
- // Layout.fillWidth: true
- // Layout.fillHeight: true
- // }
- // Text {
- // id: first
- // text: root.achievementsSummary.NumAchievedHardcore
- // font.family: Constants.regularFontFamily
- // font.pointSize: 16
- // font.weight: Font.DemiBold
- // color: "white"
- // verticalAlignment: Text.AlignBottom
- // horizontalAlignment: Text.AlignRight
- // Layout.fillHeight: true
- // }
- //
- // Text {
- // id: slash
- // text: " /"
- // Layout.fillHeight: true
- // font.family: Constants.regularFontFamily
- // font.pointSize: 15
- // color: "#aaaaaa"
- // verticalAlignment: Text.AlignBottom
- // }
- //
- // Text {
- // text: root.achievementsSummary.NumPossibleAchievements
- // Layout.fillHeight: true
- // font.family: Constants.regularFontFamily
- // font.pointSize: 12
- // color: "#aaaaaa"
- // verticalAlignment: Text.AlignBottom
- // }
- //
- // Text {
- // text: " earned"
- // Layout.fillHeight: true
- // font.family: Constants.regularFontFamily
- // font.pointSize: 10
- // color: "#aaaaaa"
- // verticalAlignment: Text.AlignBottom
- // }
- // }
- //
- // Item {
- // Layout.fillHeight: true
- // Layout.fillWidth: true
- // Layout.verticalStretchFactor: 1
- // Rectangle {
- // anchors.verticalCenter: parent.verticalCenter
- // width: parent.width
- // height: 20
- // radius: height / 2
- // color: "black"
- // }
- //
- // Rectangle {
- // anchors.verticalCenter: parent.verticalCenter
- // width: parent.width / 5
- // height: 20
- // radius: height / 2
- // border.color: "black"
- // color: "#bc8c0f"
- // }
- //
- // }
- // }
- // }
- // }
-
- // Rectangle {
- // Rectangle {
- // id: gameIcon
- // width: 80
- // height: 80
- // anchors.left: parent.left
- // anchors.top: parent.top
- // anchors.leftMargin: 12
- // anchors.topMargin: 12
- // color: "red"
- // }
- // Text {
- // text: root.entryData.display_name
- // anchors.left: gameIcon.right
- // anchors.top: parent.top
- // anchors.leftMargin: 12
- // anchors.topMargin: 12
- // color: "white"
- // font.family: Constants.regularFontFamily
- // font.pointSize: 12
- // font.weight: Font.DemiBold
- // }
- //
- // RowLayout {
- // anchors.left: parent.left
- // anchors.bottom: parent.bottom
- // anchors.right: parent.right
- // anchors.top: gameIcon.bottom
- // anchors.topMargin: 12
- // anchors.leftMargin: 12
- // anchors.bottomMargin: 12
- // spacing: 0
- //
- // Text {
- // id: first
- // text: "300"
- // font.family: Constants.regularFontFamily
- // font.pointSize: 16
- // font.weight: Font.DemiBold
- // color: "white"
- // verticalAlignment: Text.AlignBottom
- // horizontalAlignment: Text.AlignRight
- // Layout.fillHeight: true
- // }
- //
- // Text {
- // id: slash
- // text: " /"
- // Layout.fillHeight: true
- // font.family: Constants.regularFontFamily
- // font.pointSize: 15
- // color: "#aaaaaa"
- // verticalAlignment: Text.AlignBottom
- // }
- //
- // Text {
- // text: "200"
- // Layout.fillHeight: true
- // font.family: Constants.regularFontFamily
- // font.pointSize: 12
- // color: "#aaaaaa"
- // verticalAlignment: Text.AlignBottom
- // }
- //
- // Rectangle {
- // Layout.fillWidth: true
- // Layout.preferredHeight: 20
- // Layout.alignment: Qt.AlignCenter
- // radius: height / 2
- // }
- // }
- //
- // // Text {
- // // text: root.achievementsSummary.NumAchievedHardcore
- // // color: "white"
- // // font.family: Constants.regularFontFamily
- // // font.pointSize: 16
- // // font.weight: Font.DemiBold
- // // horizontalAlignment: Text.AlignHCenter
- // // verticalAlignment: Text.AlignVCenter
- // // }
- // }
-
- ListView {
- model: root.achievements
- spacing: 12
- width: parent.width
- height: contentHeight
- interactive: false
-
- // section.property: root.achievements.sortType === "title" ? "name" : "earned"
- // section.criteria: ViewSection.FirstCharacter
- // section.delegate: ListViewSectionDelegate {
- // required property string section
- // text: section === "t" || section === "f" ? (section === "t" ? "Earned" : "Not earned") : section
- // }
-
- header: Pane {
- width: achievementsList.width
- background: Item {
- }
- verticalPadding: 12
- horizontalPadding: 0
- contentItem: RowLayout {
- Item {
- Layout.fillWidth: true
- Layout.fillHeight: true
- }
- Text {
- Layout.fillHeight: true
- verticalAlignment: Text.AlignVCenter
- text: "Sort by:"
- color: "white"
- font.family: Constants.regularFontFamily
- font.pointSize: 10
- }
-
- MyComboBox {
- id: sortBox
- Layout.fillHeight: true
- Layout.fillWidth: false
- textRole: "text"
- valueRole: "value"
-
- model: [
- {text: "Default", value: "default"},
- {text: "A-Z", value: "title"},
- {text: "Earned date", value: "earned_date"},
- {text: "Points", value: "points"}
- ]
-
- Connections {
- target: root
-
- function onAchievementsChanged() {
- console.log("current sort type: " + root.achievements.sortType)
- sortBox.currentIndex = sortBox.indexOfValue(root.achievements.sortType)
- }
- }
-
- onActivated: function () {
- root.achievements.sortType = currentValue
- }
- }
- }
- }
-
- delegate: AchievementListItem {
- width: ListView.view.width
- }
- }
- }
-
-
- }
-
- Text {
- text: "activity stuff"
- color: "white"
- font.family: Constants.regularFontFamily
- font.pointSize: 10
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- }
-
- Text {
- text: "settings stuff"
- color: "white"
- font.family: Constants.regularFontFamily
- font.pointSize: 10
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- }
- }
- }
-
- Item {
- Layout.fillWidth: true
- Layout.fillHeight: true
- Layout.horizontalStretchFactor: 1
- }
- }
- }
-}
\ No newline at end of file
diff --git a/qml/library/GameGridItemDelegate.qml b/qml/library/GameGridItemDelegate.qml
new file mode 100644
index 0000000..acdd3b8
--- /dev/null
+++ b/qml/library/GameGridItemDelegate.qml
@@ -0,0 +1,135 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+FocusScope {
+ id: myDelegate
+
+ signal startGame(entryId: int)
+
+ signal openDetails(entryId: int)
+
+ signal manageSaveData(entryId: int)
+
+ required property var index
+ required property var model
+
+ required property real cellWidth
+ required property real cellHeight
+ required property real cellSpacing
+
+ width: cellWidth
+ height: cellHeight
+ Button {
+ id: button
+ padding: 0
+ anchors {
+ fill: parent
+ margins: myDelegate.cellSpacing / 2
+ }
+ focus: true
+
+ Keys.onPressed: function (event) {
+ if (event.key === Qt.Key_Menu) {
+ rightClickMenu.popup(400, 400)
+ }
+ }
+
+ TapHandler {
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+ onTapped: function (event, button) {
+ if (button === Qt.RightButton) {
+ rightClickMenu.popup()
+ } else if (button === Qt.LeftButton) {
+ // Router.navigateTo("library/" + myDelegate.model.id + "/details")
+ // myDelegate.openDetails(myDelegate.model.id)
+ myDelegate.startGame(myDelegate.model.id)
+ }
+ }
+ }
+
+ RightClickMenu {
+ id: rightClickMenu
+ objectName: "rightClickMenu"
+
+ RightClickMenuItem {
+ text: "Play " + myDelegate.model.display_name
+ onTriggered: {
+ myDelegate.startGame(myDelegate.model.id)
+ }
+ }
+
+ RightClickMenuItem {
+ enabled: false
+ text: "View details"
+ onTriggered: {
+ myDelegate.openDetails(myDelegate.model.id)
+ }
+ }
+
+ MenuSeparator {
+ contentItem: Rectangle {
+ implicitWidth: rightClickMenu.width
+ implicitHeight: 1
+ color: "#606060"
+ }
+ }
+
+ RightClickMenuItem {
+ enabled: false
+ text: "Manage save data"
+ onTriggered: {
+ myDelegate.manageSaveData(myDelegate.model.id)
+ }
+ // onTriggered: {
+ // addPlaylistRightClickMenu.entryId = libraryEntryRightClickMenu.entryId
+ // addPlaylistRightClickMenu.popup()
+ // }
+ }
+ }
+
+ hoverEnabled: true
+
+ background: Rectangle {
+ objectName: "background"
+ color: button.hovered ? "#3a3e45" : "#25282C"
+ }
+
+ contentItem: ColumnLayout {
+ Rectangle {
+ id: image
+ Layout.preferredHeight: parent.width
+ Layout.fillWidth: true
+ Layout.leftMargin: -2
+ Layout.rightMargin: -2
+
+ color: "grey"
+ }
+ Text {
+ text: myDelegate.model.display_name
+ font.pointSize: 11
+ font.weight: Font.Bold
+ // font.family: Constants.regularFontFamily
+ color: "white"
+ Layout.fillWidth: true
+ elide: Text.ElideRight
+ maximumLineCount: 1
+ // Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
+ }
+
+ Text {
+ text: myDelegate.model.platform_name
+ font.pointSize: 10
+ font.weight: Font.Medium
+ // font.family: Constants.regularFontFamily
+ color: "#C2BBBB"
+ Layout.fillWidth: true
+ // Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
+ }
+ Item {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/qml/mainmenu/MainMenuNavButton.qml b/qml/mainmenu/MainMenuNavButton.qml
deleted file mode 100644
index fb3a00b..0000000
--- a/qml/mainmenu/MainMenuNavButton.qml
+++ /dev/null
@@ -1,109 +0,0 @@
-import QtQuick
-import QtQuick.Controls
-import QtQuick.Layouts
-
-
-TabButton {
- id: myButton
-
- required property string iconCode
- required property string toolTipText
-
- implicitHeight: width
-
- contentItem: Text {
- // text: "Settings"
- text: iconCode
- color: (myHover.hovered || myButton.checked) ? "white" : "#b3b3b3"
- font.pixelSize: 28
- // font.family: Constants.strongFontFamily
- font.family: Constants.symbolFontFamily
- verticalAlignment: Text.AlignVCenter
- horizontalAlignment: Text.AlignHCenter
- }
- scale: checked ? 1.1 : 1.0
- Behavior on scale {
- NumberAnimation {
- duration: 200
- easing.type: Easing.InOutQuad
- }
- }
-
- background: Rectangle {
- color: "white"
- opacity: myButton.checked ? 0.1 : 0.0
- radius: 4
-
- anchors.centerIn: parent
-
- height: myButton.height * (2 / 3)
- width: myButton.width * (2 / 3)
-
- Behavior on opacity {
- NumberAnimation {
- duration: 200
- easing.type: Easing.InOutQuad
- }
- }
- }
-
- HoverHandler {
- id: myHover
- cursorShape: Qt.PointingHandCursor
- }
-
- ToolTip.visible: myHover.hovered
- ToolTip.text: toolTipText
- ToolTip.delay: 400
- ToolTip.timeout: 5000
-
- // ToolTip {
- // id: tool
- // visible: homeHover.hovered
- // delay: 400
- // timeout: 5000
- //
- // ParallelAnimation {
- // id: fadeIn
- // NumberAnimation {
- // target: tool
- // property: "opacity"
- // to: 1
- // duration: 200
- // easing.type: Easing.InOutQuad
- // }
- // NumberAnimation {
- // target: tool
- // property: "y"
- // from: 43
- // to: 48
- // duration: 200
- // easing.type: Easing.InOutQuad
- // }
- // }
- //
- // onVisibleChanged: function () {
- // if (visible) {
- // opacity = 0 // Start from fully transparent
- // y = 43
- // fadeIn.start()
- // }
- // }
- //
- // height: 24
- //
- // contentItem: Text {
- // text: "Home"
- // color: Constants.rightClickMenuItem_TextColor
- // font.pointSize: 12
- // font.family: Constants.regularFontFamily
- // horizontalAlignment: Text.AlignHCenter
- // verticalAlignment: Text.AlignVCenter
- // }
- //
- // background: Rectangle {
- // color: Constants.rightClickMenu_BackgroundColor
- // radius: Constants.rightClickMenu_BackgroundRadius
- // }
- // }
-}
\ No newline at end of file
diff --git a/qml/controllers/ControllersPage.qml b/qml/pages/ControllersPage.qml
similarity index 81%
rename from qml/controllers/ControllersPage.qml
rename to qml/pages/ControllersPage.qml
index 0c19574..e43e809 100644
--- a/qml/controllers/ControllersPage.qml
+++ b/qml/pages/ControllersPage.qml
@@ -202,12 +202,12 @@ Flickable {
radius: 6
}
- DetailsButton {
- anchors.right: parent.right
- anchors.rightMargin: 8
- anchors.topMargin: 8
- anchors.top: parent.top
- }
+ // DetailsButton {
+ // anchors.right: parent.right
+ // anchors.rightMargin: 8
+ // anchors.topMargin: 8
+ // anchors.top: parent.top
+ // }
Column {
id: contentColumn
@@ -239,41 +239,41 @@ Flickable {
// height: 6
// width: 1
// }
- MyComboBox {
- textRole: "text"
- valueRole: "value"
- width: parent.width
-
- // onActivated: library_short_model.sortType = currentValue
- // Component.onCompleted: currentIndex = indexOfValue(library_short_model.sortType)
-
- model: [
- {text: "Default profile", value: "display_name"},
- {text: "Newest first", value: "created_at"}
- ]
- }
- Button {
- width: parent.width
- padding: 8
- background: Rectangle {
- color: "#03438c"
- radius: 8
- }
- contentItem: Text {
- text: qsTr("Edit current profile")
- color: "white"
- font.pointSize: 11
- font.weight: Font.DemiBold
- font.family: Constants.regularFontFamily
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- }
-
- onClicked: function () {
- root.StackView.view.push(profileEditor)
- // profileDialog.open()
- }
- }
+ // MyComboBox {
+ // textRole: "text"
+ // valueRole: "value"
+ // width: parent.width
+ //
+ // // onActivated: library_short_model.sortType = currentValue
+ // // Component.onCompleted: currentIndex = indexOfValue(library_short_model.sortType)
+ //
+ // model: [
+ // {text: "Default profile", value: "display_name"},
+ // {text: "Newest first", value: "created_at"}
+ // ]
+ // }
+ // Button {
+ // width: parent.width
+ // padding: 8
+ // background: Rectangle {
+ // color: "#03438c"
+ // radius: 8
+ // }
+ // contentItem: Text {
+ // text: qsTr("Edit current profile")
+ // color: "white"
+ // font.pointSize: 11
+ // font.weight: Font.DemiBold
+ // font.family: Constants.regularFontFamily
+ // horizontalAlignment: Text.AlignHCenter
+ // verticalAlignment: Text.AlignVCenter
+ // }
+ //
+ // onClicked: function () {
+ // root.StackView.view.push(profileEditor)
+ // // profileDialog.open()
+ // }
+ // }
}
// Text {
// text: model.model_name
diff --git a/qml/DebugPage.qml b/qml/pages/DebugPage.qml
similarity index 100%
rename from qml/DebugPage.qml
rename to qml/pages/DebugPage.qml
diff --git a/qml/discover/DiscoverPage.qml b/qml/pages/DiscoverPage.qml
similarity index 100%
rename from qml/discover/DiscoverPage.qml
rename to qml/pages/DiscoverPage.qml
diff --git a/qml/EmulatorPage.qml b/qml/pages/EmulatorPage.qml
similarity index 83%
rename from qml/EmulatorPage.qml
rename to qml/pages/EmulatorPage.qml
index 4fb31e1..e133e8f 100644
--- a/qml/EmulatorPage.qml
+++ b/qml/pages/EmulatorPage.qml
@@ -14,11 +14,8 @@ Rectangle {
color: "black"
property alias currentGameName: emulatorView.currentGameName
- property alias running: emulatorView.running
- signal gameLoaded()
-
- signal readyToStart()
+ signal emulationStarted()
function loadGame(entryId) {
emulatorView.loadLibraryEntry(entryId)
@@ -40,18 +37,6 @@ Rectangle {
emulatorView.resumeGame()
}
- function isRunning() {
- return emulatorView.isRunning()
- }
-
- function startEmulation() {
- emulatorView.startEmulation()
- }
-
- function stopEmulation() {
- emulatorView.stopEmulation()
- }
-
EmulatorView {
id: emulatorView
@@ -60,6 +45,10 @@ Rectangle {
anchors.centerIn: parent
smooth: false
+ onEmulationStarted: {
+ emulatorContainer.emulationStarted()
+ }
+
// onOrphanPatchDetected: {
// console.log("orphan patch detected")
// everything.pop()
@@ -69,11 +58,6 @@ Rectangle {
// this.load(currentLibraryEntryId, romData, saveData, corePath)
// }
- onGameLoadSucceeded: function () {
- emulatorContainer.gameLoaded()
- emulatorContainer.readyToStart()
- }
-
// onReadyToStart: function () {
// emulatorContainer.readyToStart()
// }
@@ -127,5 +111,4 @@ Rectangle {
}
]
}
-}
-// color: "black"
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/qml/pages/GameDetailsPage.qml b/qml/pages/GameDetailsPage.qml
new file mode 100644
index 0000000..06635d3
--- /dev/null
+++ b/qml/pages/GameDetailsPage.qml
@@ -0,0 +1,162 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQuick.Effects
+import QtCharts 2.7
+
+FocusScope {
+ id: root
+
+ signal playPressed()
+
+ required property int entryId
+ property var achievements: null
+ property var entryData: {}
+
+ Component.onCompleted: {
+ entryData = library_database.getLibraryEntryJson(entryId)
+
+ root.achievements = achievement_manager.getAchievementsModelForGameId(entryData.game_id)
+ }
+
+ ColumnLayout {
+ spacing: 0
+ anchors.fill: parent
+
+ RowLayout {
+ id: topRow
+ Layout.fillWidth: true
+ Layout.topMargin: 12
+ Layout.minimumHeight: 84
+ Layout.maximumHeight: 84
+ spacing: 0
+
+ Text {
+ Layout.alignment: Qt.AlignTop
+ Layout.leftMargin: 48
+ padding: 10
+ text: root.entryData.display_name
+ color: "white"
+ font.pointSize: 22
+ font.family: Constants.regularFontFamily
+ font.weight: Font.DemiBold
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ Item {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ }
+
+ Button {
+ Layout.alignment: Qt.AlignRight | Qt.AlignTop
+ Layout.preferredWidth: 140
+ Layout.preferredHeight: 50
+ Layout.rightMargin: 48
+ background: Rectangle {
+ color: parent.hovered ? "#b8b8b8" : "white"
+ radius: 4
+ }
+ hoverEnabled: true
+ contentItem: Text {
+ text: qsTr("Play")
+ color: Constants.colorTestBackground
+ font.family: Constants.regularFontFamily
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ font.pointSize: 12
+ }
+ onClicked: {
+ root.playPressed()
+ }
+ }
+ }
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ spacing: 0
+
+ Item {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.horizontalStretchFactor: 1
+ }
+
+ ColumnLayout {
+ id: theColumn
+ Layout.fillWidth: true
+ Layout.horizontalStretchFactor: 4
+ Layout.minimumWidth: 700
+ Layout.maximumWidth: 1200
+ Layout.preferredWidth: parent.width * 3 / 4
+ Layout.fillHeight: true
+
+ NavigationTabBar {
+ id: bar
+ tabs: ["Details", "Achievements", "Activity", "Settings"]
+ tabWidth: 150
+ Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
+ Layout.preferredHeight: 40
+
+ onCurrentIndexChanged: function () {
+ view.setCurrentIndex(currentIndex)
+ }
+ }
+
+ SwipeView {
+ id: view
+
+ objectName: "Content Swipe View"
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ focus: true
+
+ currentIndex: 0
+
+ clip: true
+
+ onCurrentIndexChanged: function () {
+ bar.setCurrentIndex(currentIndex)
+ }
+
+ EntrySummaryPage {
+ entryData: root.entryData
+ }
+
+ AchievementList {
+ gameId: 0
+ achievementsEnabled: achievement_manager.loggedIn
+ loggedIn: achievement_manager.loggedIn
+ achievementsModel: root.achievements
+ }
+
+ Text {
+ text: "activity stuff"
+ color: "white"
+ font.family: Constants.regularFontFamily
+ font.pointSize: 10
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ Text {
+ text: "settings stuff"
+ color: "white"
+ font.family: Constants.regularFontFamily
+ font.pointSize: 10
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+ }
+ }
+
+ Item {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.horizontalStretchFactor: 1
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/qml/pages/LibraryPage.qml b/qml/pages/LibraryPage.qml
new file mode 100644
index 0000000..12264df
--- /dev/null
+++ b/qml/pages/LibraryPage.qml
@@ -0,0 +1,496 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQml.Models
+
+
+FocusScope {
+ id: root
+ objectName: "Library Focus Scope"
+
+ signal entryClicked(int entryId)
+
+ Flickable {
+ id: flick
+ objectName: "Library Flickable"
+ anchors.fill: parent
+ contentHeight: theColumn.height
+ boundsBehavior: Flickable.StopAtBounds
+ // focus: true
+
+ ScrollBar.vertical: ScrollBar {
+ }
+
+ RowLayout {
+ id: contentRow
+ anchors.fill: parent
+ spacing: 0
+
+ Item {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.horizontalStretchFactor: 1
+ }
+
+ Column {
+ id: theColumn
+ Layout.preferredWidth: Math.max(5, (Math.min(Math.floor(parent.width / libraryGrid.cellContentWidth), 8)) * libraryGrid.cellContentWidth)
+
+ Pane {
+ id: header
+
+ width: parent.width
+ height: 120
+ background: Rectangle {
+ color: "transparent"
+ // border.color: "red"
+ }
+
+ horizontalPadding: 8
+
+ Text {
+ anchors.fill: parent
+ text: "All Games"
+ color: "white"
+ font.pointSize: 26
+ font.family: Constants.regularFontFamily
+ font.weight: Font.DemiBold
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+ }
+ }
+
+ Pane {
+ id: filters
+ background: Rectangle {
+ color: "transparent"
+ // border.color: "blue"
+ }
+ width: parent.width
+
+ horizontalPadding: 8
+ verticalPadding: 0
+
+ RowLayout {
+ anchors.fill: parent
+
+ spacing: 12
+ Text {
+ Layout.fillHeight: true
+ Layout.alignment: Qt.AlignBottom
+ text: "Showing " + library_model.count + " game" + (library_model.count === 1 ? "" : "s")
+ color: "#C2BBBB"
+ font.pointSize: 11
+ font.family: Constants.regularFontFamily
+ font.weight: Font.Normal
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignBottom
+ }
+
+ Item {
+ Layout.fillWidth: true
+ }
+
+ // Button {
+ // id: melol
+ // Layout.preferredHeight: 32
+ // horizontalPadding: 12
+ // verticalPadding: 8
+ //
+ // hoverEnabled: true
+ //
+ // Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
+ // background: Rectangle {
+ // color: melol.hovered ? "#4e535b" : "#3e434b"
+ // radius: 12
+ // border.color: "#7d848c"
+ // }
+ //
+ // contentItem: Text {
+ // text: "Show filters"
+ // color: "white"
+ // font.pointSize: 11
+ // font.family: Constants.regularFontFamily
+ // horizontalAlignment: Text.AlignHCenter
+ // verticalAlignment: Text.AlignVCenter
+ // }
+ // }
+
+ // Item {
+ // Layout.preferredWidth: 8
+ // }
+
+ Text {
+ Layout.fillHeight: true
+ Layout.alignment: Qt.AlignVCenter
+ text: "Sort by:"
+ color: "white"
+ font.pointSize: 12
+ font.family: Constants.regularFontFamily
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ MyComboBox {
+ textRole: "text"
+ valueRole: "value"
+
+ onActivated: library_short_model.sortType = currentValue
+ Component.onCompleted: currentIndex = indexOfValue(library_short_model.sortType)
+
+ model: [
+ {text: "A-Z", value: "display_name"},
+ {text: "Newest first", value: "created_at"}
+ ]
+ }
+ }
+ }
+
+ Pane {
+ background: Rectangle {
+ color: "transparent"
+ // border.color: "green"
+ }
+ width: parent.width
+ height: libraryGrid.contentHeight
+
+ horizontalPadding: 0
+
+ GridView {
+ id: libraryGrid
+ width: parent.width
+ height: 2000
+ cellWidth: cellContentWidth
+ cellHeight: cellContentHeight
+ focus: true
+
+ function itemsPerRow() {
+ return Math.floor(width / cellWidth);
+ }
+
+ function rowWidth() {
+ return itemsPerRow() * cellWidth;
+ }
+
+ // onWidthChanged: {
+ // contentRow.middleSectionWidth = libraryGrid.rowWidth()
+ // }
+
+ cacheBuffer: 30
+
+ clip: true
+
+ interactive: false
+ readonly property int cellSpacing: 12
+ readonly property int cellContentWidth: 180 + cellSpacing
+ readonly property int cellContentHeight: 260 + cellSpacing
+
+ populate: Transition {
+ }
+
+ currentIndex: 0
+
+ model: library_short_model
+ boundsBehavior: Flickable.StopAtBounds
+ keyNavigationEnabled: true
+
+ highlight: Item {
+ }
+
+ delegate: FocusScope {
+ id: rootItem
+ width: libraryGrid.cellContentWidth
+ height: libraryGrid.cellContentHeight
+
+ Button {
+ id: libItemButton
+ anchors {
+ fill: parent
+ margins: libraryGrid.cellSpacing / 2
+ }
+
+ scale: pressed ? 0.97 : 1
+ Behavior on scale {
+ NumberAnimation {
+ duration: 64
+ easing.type: Easing.InOutQuad
+ }
+ }
+
+ background: Rectangle {
+ color: libItemButton.hovered ? "#3a3e45" : "#25282C"
+ radius: 6
+ // border.color: rootItem.GridView.isCurrentItem ? "white" : "transparent"
+ // border.width: 2
+ // leftInset: -2
+ // rightInset: -2
+ // topInset: -2
+ // bottomInset: -2
+ }
+
+ contentItem: ColumnLayout {
+ id: colLayout
+ spacing: 0
+ anchors {
+ fill: parent
+ // margins: 2
+ }
+
+ // Image {
+ // asynchronous: true
+ // source: "file:system/_img/pmbox.jpg"
+ // // Layout.fillWidth: true
+ // // Layout.preferredHeight: 180
+ //
+ // // implicitWidth: libItemMouse.hovered ? parent.width + 10 : parent.width
+ // // implicitHeight: libItemMouse.hovered ? parent.height + 10 : parent.height
+ //
+ // // scale: libItemMouse.hovered ? 1.02 : 1
+ //
+ // // Behavior on scale {
+ // // NumberAnimation {
+ // // duration: 200
+ // // easing.type: Easing.InOutQuad
+ // // }
+ // // }
+ //
+ // width: 200
+ // height: 200
+ //
+ // sourceSize.width: 180
+ // sourceSize.height: 180
+ //
+ // horizontalAlignment: Image.AlignLeft
+ //
+ // fillMode: Image.PreserveAspectCrop
+ // }
+
+ Rectangle {
+ color: "grey"
+ Layout.fillWidth: true
+ Layout.preferredHeight: colLayout.width
+ topLeftRadius: 6
+ topRightRadius: 6
+
+ Text {
+ text: "Box art coming soon :)"
+ font.pointSize: 9
+ // font.weight: Font.Light
+ font.family: Constants.regularFontFamily
+ color: "#232323"
+ anchors.centerIn: parent
+ // Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
+ }
+ }
+
+ Item {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ ColumnLayout {
+ spacing: 2
+ anchors {
+ fill: parent
+ margins: 6
+ }
+
+ Text {
+ text: model.display_name
+ font.pointSize: 11
+ font.weight: Font.Bold
+ font.family: Constants.regularFontFamily
+ color: "white"
+ Layout.fillWidth: true
+ elide: Text.ElideRight
+ maximumLineCount: 1
+ // Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
+ }
+ Text {
+ text: model.platform_name
+ font.pointSize: 10
+ font.weight: Font.Medium
+ font.family: Constants.regularFontFamily
+ color: "#C2BBBB"
+ Layout.fillWidth: true
+ // Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
+ }
+ Item {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ }
+ }
+ }
+
+ // Pane {
+ // Layout.fillWidth: true
+ // Layout.fillHeight: true
+ //
+ // // clip: true
+ // background: Item {
+ // }
+ // padding: 8
+ // ColumnLayout {
+ // anchors.fill: parent
+ // spacing: 2
+ // Text {
+ // text: model.display_name
+ // font.pointSize: 11
+ // font.weight: Font.Bold
+ // font.family: Constants.regularFontFamily
+ // color: "white"
+ // Layout.fillWidth: true
+ // elide: Text.ElideRight
+ // maximumLineCount: 1
+ // // Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
+ // }
+ // Text {
+ // text: model.platform_name
+ // font.pointSize: 10
+ // font.weight: Font.Medium
+ // font.family: Constants.regularFontFamily
+ // color: "#C2BBBB"
+ // Layout.fillWidth: true
+ // // Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
+ // }
+ // Item {
+ // Layout.fillWidth: true
+ // Layout.fillHeight: true
+ // }
+ // }
+ //
+ // }
+ }
+
+ TapHandler {
+ acceptedButtons: Qt.RightButton
+ onTapped: {
+ rootItem.GridView.view.currentIndex = index
+ libraryEntryRightClickMenu.popup()
+ }
+ }
+
+ Keys.onReturnPressed: {
+ doubleClicked()
+ }
+
+ onClicked: function () {
+ rootItem.GridView.view.currentIndex = model.index
+ root.StackView.view.push(gameDetailsPage, {
+ "objectName": "Game Details Page",
+ "entryId": model.id
+ })
+ // libraryEntryRightClickMenu.popup()
+ }
+
+ onDoubleClicked: function () {
+ entryClicked(model.id)
+ }
+
+ RightClickMenu {
+ id: libraryEntryRightClickMenu
+
+ RightClickMenuItem {
+ text: "Play " + model.display_name
+ onTriggered: {
+ entryClicked(model.id)
+ }
+ }
+
+ // RightClickMenuItem {
+ // text: "Patch stuff"
+ // visible: model.parent_game_name !== ""
+ // onTriggered: function () {
+ // selectLibEntryDialog.open()
+ // }
+ // }
+
+ RightClickMenuItem {
+ // enabled: false
+ text: "View details"
+ onTriggered: {
+ root.StackView.view.push(gameDetailsPage, {"entryId": model.id})
+ }
+ }
+
+ MenuSeparator {
+ contentItem: Rectangle {
+ implicitWidth: 188
+ implicitHeight: 1
+ color: "#606060"
+ }
+ }
+
+ RightClickMenu {
+ id: addPlaylistRightClickMenu
+ enabled: ins.count > 0
+
+ title: "Add to folder"
+
+ Instantiator {
+ id: ins
+ model: playlist_model
+ delegate: RightClickMenuItem {
+ text: model.display_name
+ onTriggered: {
+ playlist_model.addEntryToPlaylist(model.id, libraryEntryRightClickMenu.entryId)
+ library_model.updatePlaylistsForEntry(libraryEntryRightClickMenu.entryId)
+ // Add your action here
+ }
+ }
+
+ onObjectAdded: function (index, object) {
+ addPlaylistRightClickMenu.insertItem(index, object)
+ }
+ onObjectRemoved: function (index, object) {
+ addPlaylistRightClickMenu.removeItem(object)
+ }
+ }
+ }
+
+ MenuSeparator {
+ contentItem: Rectangle {
+ implicitWidth: 188
+ implicitHeight: 1
+ color: "#606060"
+ }
+ }
+
+ RightClickMenuItem {
+ enabled: false
+ text: "Manage save data"
+ // onTriggered: {
+ // addPlaylistRightClickMenu.entryId = libraryEntryRightClickMenu.entryId
+ // addPlaylistRightClickMenu.popup()
+ // }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Item {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.horizontalStretchFactor: 1
+ }
+ }
+
+ SelectLibraryEntryDialog {
+ id: selectLibEntryDialog
+ }
+
+ Component {
+ id: gameDetailsPage
+ GameDetailsPage {
+ objectName: "GameDetailsPage"
+ entryId: -1
+
+ onPlayPressed: function () {
+ entryClicked(entryId)
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/qml/pages/LibraryPage2.qml b/qml/pages/LibraryPage2.qml
new file mode 100644
index 0000000..60ceb25
--- /dev/null
+++ b/qml/pages/LibraryPage2.qml
@@ -0,0 +1,75 @@
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls
+
+FocusScope {
+ id: page
+ property alias model: listView.model
+
+ signal openDetails(entryId: int)
+ signal startGame(entryId: int)
+
+ GridView {
+ id: listView
+ objectName: "GridView"
+ clip: true
+ width: Math.min(Math.floor(parent.width / cellContentWidth), 7) * cellContentWidth
+ anchors.horizontalCenter: parent.horizontalCenter
+ height: parent.height
+
+ preferredHighlightBegin: 0.33 * listView.height
+ preferredHighlightEnd: 0.66 * listView.height
+ highlightRangeMode: GridView.ApplyRange
+
+ contentY: 0
+
+ header: Pane {
+ id: header
+
+ width: parent.width
+ height: 120
+ background: Rectangle {
+ color: "transparent"
+ // border.color: "red"
+ }
+
+ horizontalPadding: 8
+
+ Text {
+ anchors.fill: parent
+ text: "All Games"
+ color: "white"
+ font.pointSize: 26
+ font.family: Constants.regularFontFamily
+ font.weight: Font.DemiBold
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ }
+
+ readonly property int cellSpacing: 12
+ readonly property int cellContentWidth: 180 + cellSpacing
+ readonly property int cellContentHeight: 260 + cellSpacing
+
+ cellWidth: cellContentWidth
+ cellHeight: cellContentHeight
+ focus: true
+
+ currentIndex: 0
+ boundsBehavior: Flickable.StopAtBounds
+
+ delegate: GameGridItemDelegate {
+ cellSpacing: listView.cellSpacing
+ cellWidth: listView.cellContentWidth
+ cellHeight: listView.cellContentHeight
+
+ Component.onCompleted: {
+ openDetails.connect(page.openDetails)
+ startGame.connect(page.startGame)
+ }
+ }
+ }
+}
+
diff --git a/qml/NowPlayingPage.qml b/qml/pages/NowPlayingPage.qml
similarity index 87%
rename from qml/NowPlayingPage.qml
rename to qml/pages/NowPlayingPage.qml
index e8d9f59..ee2fb7a 100644
--- a/qml/NowPlayingPage.qml
+++ b/qml/pages/NowPlayingPage.qml
@@ -3,7 +3,7 @@ import QtQuick.Controls
import QtQuick.Layouts
-Item {
+FocusScope {
id: root
signal resumeGamePressed()
@@ -14,9 +14,28 @@ Item {
signal closeGamePressed()
+ Item {
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height: 40
+ }
+ // NavigationTabBar {
+ // id: navBar
+ // anchors.horizontalCenter: parent.horizontalCenter
+ //
+ // tabs: ["Stuff", "Controller", "Achievements", "Settings"]
+ // tabWidth: 150
+ // height: 40
+ // }
+
RowLayout {
id: contentRow
- anchors.fill: parent
+ // anchors.top: navBar.bottom
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
spacing: 0
Item {
@@ -73,23 +92,6 @@ Item {
opacity: 0.3
color: "#dadada"
}
- FirelightMenuItem {
- labelText: "Achievements"
- Layout.fillWidth: true
- // Layout.preferredWidth: parent.width / 2
- Layout.alignment: Qt.AlignLeft | Qt.AlignTop
- Layout.preferredHeight: 40
- checkable: true
- alignRight: true
- }
- Rectangle {
- Layout.fillWidth: true
- // Layout.preferredWidth: parent.width / 2
- Layout.preferredHeight: 1
- Layout.alignment: Qt.AlignLeft | Qt.AlignTop
- opacity: 0.3
- color: "#dadada"
- }
FirelightMenuItem {
labelText: "Create Suspend Point"
Layout.fillWidth: true
@@ -125,15 +127,6 @@ Item {
opacity: 0.3
color: "#dadada"
}
- FirelightMenuItem {
- labelText: "Settings"
- Layout.fillWidth: true
- // Layout.preferredWidth: parent.width / 2
- Layout.alignment: Qt.AlignLeft | Qt.AlignTop
- Layout.preferredHeight: 40
- checkable: true
- alignRight: true
- }
FirelightMenuItem {
labelText: "Back to Main Menu"
Layout.fillWidth: true
@@ -142,6 +135,7 @@ Item {
Layout.preferredHeight: 40
checkable: false
alignRight: true
+ enabled: false
onClicked: function () {
backToMainMenuPressed()
diff --git a/qml/discover/StoreContent.qml b/qml/pages/StoreContent.qml
similarity index 100%
rename from qml/discover/StoreContent.qml
rename to qml/pages/StoreContent.qml
diff --git a/qml/platforms/GameBoyColorSettings.qml b/qml/platforms/GameBoyColorSettings.qml
new file mode 100644
index 0000000..e56fb97
--- /dev/null
+++ b/qml/platforms/GameBoyColorSettings.qml
@@ -0,0 +1,192 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+FocusScope {
+ id: root
+ Flickable {
+ anchors.fill: parent
+ contentHeight: column.height
+ ColumnLayout {
+ id: column
+ width: parent.width
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 40
+ spacing: 8
+
+ Button {
+ id: backButton
+ background: Rectangle {
+ color: enabled ? (backButton.hovered ? "#404143" : "transparent") : "transparent"
+ radius: height / 2
+
+ }
+
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height
+
+ hoverEnabled: true
+
+ contentItem: Text {
+ text: "\ue5e0"
+ font.family: Constants.symbolFontFamily
+ leftPadding: 8
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ font.pixelSize: 24
+ color: ColorPalette.neutral400
+ }
+
+ checkable: false
+
+ onClicked: {
+ root.StackView.view.pop()
+ }
+ }
+
+ Text {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ text: qsTr("Default Game Boy Color settings")
+ font.pixelSize: 26
+ font.family: Constants.regularFontFamily
+ font.weight: Font.Bold
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+ color: ColorPalette.neutral100
+ }
+ Layout.bottomMargin: 8
+ }
+
+ Rectangle {
+ Layout.topMargin: 8
+ Layout.bottomMargin: 8
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ OptionGroup {
+ Layout.topMargin: 30
+ Layout.fillWidth: true
+ label: "Display"
+
+ content: [
+ ToggleOption {
+ Layout.fillWidth: true
+ label: "Simulate LCD ghosting effects"
+ // description: "Enables simulation of LCD ghosting effects by blending the current and previous frames."
+ checked: emulator_config_manager.getOptionValueForPlatform(2, "gambatte_mix_frames") === "accurate"
+
+ // Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "gambatte_mix_frames") === "accurate"
+ // }
+
+ onCheckedChanged: {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(2, "gambatte_mix_frames", "accurate")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(2, "gambatte_mix_frames", "disabled")
+ }
+ }
+
+ },
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ },
+ ToggleOption {
+ id: colorCorrectionOption
+ Layout.fillWidth: true
+ label: "Adjust output colors to match original hardware"
+ // label: "Color correction"
+ // description: "Make the screen colors more accurate to the original hardware."
+
+ checked: emulator_config_manager.getOptionValueForPlatform(2, "gambatte_gbc_color_correction") === "GBC only"
+
+ onCheckedChanged: function () {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(2, "gambatte_gbc_color_correction", "GBC only")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(2, "gambatte_gbc_color_correction", "disabled")
+ }
+ }
+ },
+
+ Option {
+ Layout.fillWidth: true
+ // Layout.leftMargin: 36
+ isSubItem: true
+ visible: colorCorrectionOption.checked
+ label: "Frontlight position"
+ // description: "Simulates the physical response of the Game Boy Color LCD panel when illuminated from different angles."
+ control: MyComboBox {
+ id: frontlightPositionComboBox
+ enabled: colorCorrectionOption.checked
+ model: [{
+ text: "Center of screen",
+ value: "central"
+ }, {
+ text: "Above screen",
+ value: "above screen"
+ }, {
+ text: "Below screen",
+ value: "below screen"
+ }]
+ textRole: "text"
+ valueRole: "value"
+
+ Component.onCompleted: {
+ const val = frontlightPositionComboBox.indexOfValue(emulator_config_manager.getOptionValueForPlatform(2, "gambatte_gbc_frontlight_position"))
+ console.log("VALUE: " + val)
+ frontlightPositionComboBox.currentIndex = val
+ }
+
+ onActivated: function (index) {
+ if (!currentValue) {
+ return
+ }
+ emulator_config_manager.setOptionValueForPlatform(2, "gambatte_gbc_frontlight_position", currentValue)
+ }
+ }
+ },
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ },
+
+ Option {
+ Layout.fillWidth: true
+ label: "Darken screen to reduce harshness"
+ },
+
+ MySlider {
+ Layout.fillWidth: true
+ // Layout.preferredHeight: 48
+ from: 0
+ to: 50
+ stepSize: 5
+ snapMode: Slider.SnapOnRelease
+ live: false
+
+ value: emulator_config_manager.getOptionValueForPlatform(2, "gambatte_dark_filter_level")
+
+ onValueChanged: function () {
+ emulator_config_manager.setOptionValueForPlatform(2, "gambatte_dark_filter_level", value)
+ }
+ }
+
+
+ ]
+ }
+
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/qml/platforms/GameBoySettings.qml b/qml/platforms/GameBoySettings.qml
new file mode 100644
index 0000000..d3cf0fa
--- /dev/null
+++ b/qml/platforms/GameBoySettings.qml
@@ -0,0 +1,457 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+FocusScope {
+ id: root
+ Flickable {
+ anchors.fill: parent
+ contentHeight: column.height
+ ColumnLayout {
+ id: column
+ width: parent.width
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 40
+ spacing: 8
+
+ Button {
+ id: backButton
+ background: Rectangle {
+ color: enabled ? (backButton.hovered ? "#404143" : "transparent") : "transparent"
+ radius: height / 2
+
+ }
+
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height
+
+ hoverEnabled: true
+
+ contentItem: Text {
+ text: "\ue5e0"
+ font.family: Constants.symbolFontFamily
+ leftPadding: 8
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ font.pixelSize: 24
+ color: ColorPalette.neutral400
+ }
+
+ checkable: false
+
+ onClicked: {
+ root.StackView.view.pop()
+ }
+ }
+
+ Text {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ text: qsTr("Default Game Boy settings")
+ font.pixelSize: 26
+ font.family: Constants.regularFontFamily
+ font.weight: Font.Bold
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+ color: ColorPalette.neutral100
+ }
+ Layout.bottomMargin: 8
+ }
+
+ Rectangle {
+ Layout.topMargin: 8
+ Layout.bottomMargin: 8
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ OptionGroup {
+ Layout.fillWidth: true
+ label: "Colorization"
+
+ content: [
+ Option {
+ id: colorizePalette
+ Layout.fillWidth: true
+ // Layout.leftMargin: 36
+ label: "Colorization mode"
+ property string currentValue
+ // description: "Simulates the physical response of the Game Boy Color LCD panel when illuminated from different angles."
+ control: MyComboBox {
+ model: [{
+ text: "Disabled",
+ value: "disabled"
+ }, {
+ text: "Auto",
+ value: "auto"
+ }, {
+ text: "Specific palette",
+ value: "internal"
+ }]
+ textRole: "text"
+ valueRole: "value"
+
+ Component.onCompleted: {
+ const value = emulator_config_manager.getOptionValueForPlatform(1, "gambatte_gb_colorization")
+
+ if (value) {
+ currentIndex = indexOfValue(value)
+ colorizePalette.currentValue = value
+ } else {
+ currentIndex = 0
+ }
+ }
+
+ onActivated: function (index) {
+ emulator_config_manager.setOptionValueForPlatform(1, "gambatte_gb_colorization", currentValue)
+ colorizePalette.currentValue = currentValue
+ }
+ }
+ },
+ Option {
+ id: colorizationOption
+ Layout.fillWidth: true
+ // Layout.leftMargin: 36
+ isSubItem: true
+ visible: colorizePalette.currentValue === "internal"
+ label: "Color palette"
+ property string currentValue
+ control: MyComboBox {
+ enabled: colorizePalette.currentValue === "internal"
+ model: [{
+ text: "GB - DMG",
+ value: "GB - DMG"
+ }, {
+ text: "GB - Pocket",
+ value: "GB - Pocket"
+ }, {
+ text: "GB - Light",
+ value: "GB - Light"
+ }, {
+ text: "GBC - Blue",
+ value: "GBC - Blue"
+ }, {
+ text: "GBC - Brown",
+ value: "GBC - Brown"
+ }, {
+ text: "GBC - Dark Blue",
+ value: "GBC - Dark Blue"
+ }, {
+ text: "GBC - Dark Brown",
+ value: "GBC - Dark Brown"
+ }, {
+ text: "GBC - Dark Green",
+ value: "GBC - Dark Green"
+ }, {
+ text: "GBC - Grayscale",
+ value: "GBC - Grayscale"
+ }, {
+ text: "GBC - Green",
+ value: "GBC - Green"
+ }, {
+ text: "GBC - Inverted",
+ value: "GBC - Inverted"
+ }, {
+ text: "GBC - Orange",
+ value: "GBC - Orange"
+ }, {
+ text: "GBC - Pastel Mix",
+ value: "GBC - Pastel Mix"
+ }, {
+ text: "GBC - Red",
+ value: "GBC - Red"
+ }, {
+ text: "GBC - Yellow",
+ value: "GBC - Yellow"
+ }, {
+ text: "SGB - 1A",
+ value: "SGB - 1A"
+ }, {
+ text: "SGB - 1B",
+ value: "SGB - 1B"
+ }, {
+ text: "SGB - 1C",
+ value: "SGB - 1C"
+ }, {
+ text: "SGB - 1D",
+ value: "SGB - 1D"
+ }, {
+ text: "SGB - 1E",
+ value: "SGB - 1E"
+ }, {
+ text: "SGB - 1F",
+ value: "SGB - 1F"
+ }, {
+ text: "SGB - 1G",
+ value: "SGB - 1G"
+ }, {
+ text: "SGB - 1H",
+ value: "SGB - 1H"
+ }, {
+ text: "SGB - 2A",
+ value: "SGB - 2A"
+ }, {
+ text: "SGB - 2B",
+ value: "SGB - 2B"
+ }, {
+ text: "SGB - 2C",
+ value: "SGB - 2C"
+ }, {
+ text: "SGB - 2D",
+ value: "SGB - 2D"
+ }, {
+ text: "SGB - 2E",
+ value: "SGB - 2E"
+ }, {
+ text: "SGB - 2F",
+ value: "SGB - 2F"
+ }, {
+ text: "SGB - 2G",
+ value: "SGB - 2G"
+ }, {
+ text: "SGB - 2H",
+ value: "SGB - 2H"
+ }, {
+ text: "SGB - 3A",
+ value: "SGB - 3A"
+ }, {
+ text: "SGB - 3B",
+ value: "SGB - 3B"
+ }, {
+ text: "SGB - 3C",
+ value: "SGB - 3C"
+ }, {
+ text: "SGB - 3D",
+ value: "SGB - 3D"
+ }, {
+ text: "SGB - 3E",
+ value: "SGB - 3E"
+ }, {
+ text: "SGB - 3F",
+ value: "SGB - 3F"
+ }, {
+ text: "SGB - 3G",
+ value: "SGB - 3G"
+ }, {
+ text: "SGB - 3H",
+ value: "SGB - 3H"
+ }, {
+ text: "SGB - 4A",
+ value: "SGB - 4A"
+ }, {
+ text: "SGB - 4B",
+ value: "SGB - 4B"
+ }, {
+ text: "SGB - 4C",
+ value: "SGB - 4C"
+ }, {
+ text: "SGB - 4D",
+ value: "SGB - 4D"
+ }, {
+ text: "SGB - 4E",
+ value: "SGB - 4E"
+ }, {
+ text: "SGB - 4F",
+ value: "SGB - 4F"
+ }, {
+ text: "SGB - 4G",
+ value: "SGB - 4G"
+ }, {
+ text: "SGB - 4H",
+ value: "SGB - 4H"
+ }, {
+ text: "Special 1",
+ value: "Special 1"
+ }, {
+ text: "Special 2",
+ value: "Special 2"
+ }, {
+ text: "Special 3",
+ value: "Special 3"
+ }]
+ textRole: "text"
+ valueRole: "value"
+
+ Component.onCompleted: {
+ const value = emulator_config_manager.getOptionValueForPlatform(1, "gambatte_gb_internal_palette")
+
+ if (value) {
+ currentIndex = indexOfValue(value)
+ colorizationOption.currentValue = value
+ } else {
+ currentIndex = 0
+ }
+ }
+
+ onActivated: function (index) {
+ emulator_config_manager.setOptionValueForPlatform(1, "gambatte_gb_internal_palette", currentValue)
+ colorizationOption.currentValue = currentValue
+ }
+ }
+ },
+ Text {
+ Layout.topMargin: 8
+ Layout.fillWidth: true
+ visible: colorizePalette.currentValue === "internal"
+ text: "Example"
+ color: ColorPalette.neutral300
+ horizontalAlignment: Text.AlignHCenter
+ font.pixelSize: 15
+ Layout.alignment: Qt.AlignLeft
+ font.family: Constants.regularFontFamily
+ font.weight: Font.DemiBold
+ },
+ Item {
+ Layout.topMargin: 8
+ Layout.bottomMargin: 16
+ Layout.fillWidth: true
+ Layout.preferredHeight: 400
+ visible: colorizePalette.currentValue === "internal"
+ Image {
+ id: colorPreviewImg
+ anchors.fill: parent
+ source: "file:system/_img/gb-dmg.png"
+ fillMode: Image.PreserveAspectFit
+
+ Connections {
+ target: colorizationOption
+
+ function onCurrentValueChanged() {
+ let filename = colorizationOption.currentValue ? colorizationOption.currentValue.toLowerCase() : ""
+ let removedSpacesText = filename.replace(
+ / /g,
+ ""
+ );
+ colorPreviewImg.source = "file:system/_img/" + removedSpacesText + ".png"
+ }
+ }
+ }
+ }
+ ]
+ }
+
+ OptionGroup {
+ Layout.topMargin: 30
+ Layout.fillWidth: true
+ label: "Display"
+
+ content: [
+ ToggleOption {
+ Layout.fillWidth: true
+ label: "Simulate LCD ghosting effects"
+ // description: "Enables simulation of LCD ghosting effects by blending the current and previous frames."
+ checked: emulator_config_manager.getOptionValueForPlatform(1, "gambatte_mix_frames") === "accurate"
+
+ // Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "gambatte_mix_frames") === "accurate"
+ // }
+
+ onCheckedChanged: {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(1, "gambatte_mix_frames", "accurate")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(1, "gambatte_mix_frames", "disabled")
+ }
+ }
+
+ },
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ },
+ ToggleOption {
+ id: colorCorrectionOption
+ Layout.fillWidth: true
+ label: "Adjust output colors to match original hardware"
+ // label: "Color correction"
+ // description: "Make the screen colors more accurate to the original hardware."
+
+ checked: emulator_config_manager.getOptionValueForPlatform(1, "gambatte_gbc_color_correction") === "GBC only"
+
+ onCheckedChanged: function () {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(1, "gambatte_gbc_color_correction", "GBC only")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(1, "gambatte_gbc_color_correction", "disabled")
+ }
+ }
+ },
+
+ Option {
+ Layout.fillWidth: true
+ // Layout.leftMargin: 36
+ isSubItem: true
+ visible: colorCorrectionOption.checked
+ label: "Frontlight position"
+ // description: "Simulates the physical response of the Game Boy Color LCD panel when illuminated from different angles."
+ control: MyComboBox {
+ id: frontlightPositionComboBox
+ enabled: colorCorrectionOption.checked
+ model: [{
+ text: "Center of screen",
+ value: "central"
+ }, {
+ text: "Above screen",
+ value: "above screen"
+ }, {
+ text: "Below screen",
+ value: "below screen"
+ }]
+ textRole: "text"
+ valueRole: "value"
+
+ Component.onCompleted: {
+ const val = frontlightPositionComboBox.indexOfValue(emulator_config_manager.getOptionValueForPlatform(1, "gambatte_gbc_frontlight_position"))
+ console.log("VALUE: " + val)
+ frontlightPositionComboBox.currentIndex = val
+ }
+
+ onActivated: function (index) {
+ if (!currentValue) {
+ return
+ }
+ emulator_config_manager.setOptionValueForPlatform(1, "gambatte_gbc_frontlight_position", currentValue)
+ }
+ }
+ },
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ },
+
+ Option {
+ Layout.fillWidth: true
+ label: "Darken screen to reduce harshness"
+ },
+
+ MySlider {
+ Layout.fillWidth: true
+ // Layout.preferredHeight: 48
+ from: 0
+ to: 50
+ stepSize: 5
+ snapMode: Slider.SnapOnRelease
+ live: false
+
+ value: emulator_config_manager.getOptionValueForPlatform(1, "gambatte_dark_filter_level")
+
+ onValueChanged: function () {
+ emulator_config_manager.setOptionValueForPlatform(1, "gambatte_dark_filter_level", value)
+ }
+ }
+
+
+ ]
+ }
+
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/qml/platforms/GbaSettings.qml b/qml/platforms/GbaSettings.qml
new file mode 100644
index 0000000..58d952e
--- /dev/null
+++ b/qml/platforms/GbaSettings.qml
@@ -0,0 +1,215 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+FocusScope {
+ id: root
+ Flickable {
+ anchors.fill: parent
+ contentHeight: column.height
+ ColumnLayout {
+ id: column
+ width: parent.width
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 40
+ spacing: 8
+
+ Button {
+ id: backButton
+ background: Rectangle {
+ color: enabled ? (backButton.hovered ? "#404143" : "transparent") : "transparent"
+ radius: height / 2
+
+ }
+
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height
+
+ hoverEnabled: true
+
+ contentItem: Text {
+ text: "\ue5e0"
+ font.family: Constants.symbolFontFamily
+ leftPadding: 8
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ font.pixelSize: 24
+ color: ColorPalette.neutral400
+ }
+
+ checkable: false
+
+ onClicked: {
+ root.StackView.view.pop()
+ }
+ }
+
+ Text {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ text: qsTr("Default Game Boy Advance settings")
+ font.pixelSize: 26
+ font.family: Constants.regularFontFamily
+ font.weight: Font.Bold
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+ color: ColorPalette.neutral100
+ }
+ Layout.bottomMargin: 8
+ }
+
+ Rectangle {
+ Layout.topMargin: 8
+ Layout.bottomMargin: 8
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ OptionGroup {
+ Layout.fillWidth: true
+ label: "General"
+
+ content: [
+ // ToggleOption {
+ // Layout.fillWidth: true
+ // label: "Allow opposing D-Pad directions"
+ // description: "Enabling this will allow pressing / quickly alternating / holding both left and right (or up and down) directions at the same time. This may cause movement-based glitches."
+ // },
+ //
+ // Rectangle {
+ // Layout.fillWidth: true
+ // Layout.preferredHeight: 1
+ // color: "#333333"
+ // },
+
+ ToggleOption {
+ Layout.fillWidth: true
+ label: "Filter out overly harsh audio"
+ // description: "Enables a low pass audio filter to reduce the 'harshness' of generated audio."
+
+ checked: emulator_config_manager.getOptionValueForPlatform(3, "mgba_audio_low_pass_filter") === "enabled"
+
+ onCheckedChanged: {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(3, "mgba_audio_low_pass_filter", "enabled")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(3, "mgba_audio_low_pass_filter", "disabled")
+ }
+ }
+ },
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ },
+
+ ToggleOption {
+ Layout.fillWidth: true
+ label: "Adjust output colors to match original hardware"
+ // description: "Adjusts output colors to match the display of real GBA hardware."
+
+ checked: emulator_config_manager.getOptionValueForPlatform(3, "mgba_color_correction") === "Auto"
+
+ onCheckedChanged: {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(3, "mgba_color_correction", "Auto")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(3, "mgba_color_correction", "OFF")
+ }
+ }
+ },
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ },
+
+ // ToggleOption {
+ // Layout.fillWidth: true
+ // label: "Game Boy Player Rumble (Restart)"
+ // description: "Enabling this will allow compatible games with the Game Boy Player boot logo to make the controller rumble. Due to how Nintendo decided this feature should work, it may cause glitches such as flickering or lag in some of these games."
+ // },
+ //
+ // Rectangle {
+ // Layout.fillWidth: true
+ // Layout.preferredHeight: 1
+ // color: "#333333"
+ // },
+
+ // ToggleOption {
+ // Layout.fillWidth: true
+ // label: "Idle Loop Optimization"
+ // description: "Reduce system load by optimizing so-called 'idle-loops' - sections in the code where nothing happens, but the CPU runs at full speed (like a car revving in neutral). Improves performance, and should be enabled on low-end hardware."
+ // },
+ //
+ // Rectangle {
+ // Layout.fillWidth: true
+ // Layout.preferredHeight: 1
+ // color: "#333333"
+ // },
+
+ ToggleOption {
+ Layout.fillWidth: true
+ label: "Simulate LCD ghosting effects"
+ // description: "Simulates LCD ghosting effects. 'Simple' performs a 50:50 mix of the current and previous frames. 'Smart' attempts to detect screen flickering, and only performs a 50:50 mix on affected pixels. 'LCD Ghosting' mimics natural LCD response times by combining multiple buffered frames. 'Simple' or 'Smart' blending is required when playing games that aggressively exploit LCD ghosting for transparency effects (Wave Race, Chikyuu Kaihou Gun ZAS, F-Zero, the Boktai series...)."
+
+ checked: emulator_config_manager.getOptionValueForPlatform(3, "mgba_interframe_blending") === "mix_smart"
+
+ onCheckedChanged: {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(3, "mgba_interframe_blending", "mix_smart")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(3, "mgba_interframe_blending", "OFF")
+ }
+ }
+ },
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ },
+
+ Option {
+ Layout.fillWidth: true
+ label: "Solar sensor level"
+ // description: "Sets ambient sunlight intensity. Can be used by games that included a solar sensor in their cartridges, e.g: the Boktai series."
+ },
+
+ MySlider {
+ Layout.fillWidth: true
+ // Layout.preferredHeight: 48
+ from: 0
+ to: 100
+ stepSize: 10
+ snapMode: Slider.SnapOnRelease
+ live: false
+
+ value: emulator_config_manager.getOptionValueForPlatform(3, "mgba_solar_sensor_level") * 10
+
+ onValueChanged: function () {
+ emulator_config_manager.setOptionValueForPlatform(3, "mgba_solar_sensor_level", value / 10)
+ }
+ }
+
+ // Rectangle {
+ // Layout.fillWidth: true
+ // Layout.preferredHeight: 1
+ // color: "#333333"
+ // },
+ //
+ // ToggleOption {
+ // Layout.fillWidth: true
+ // label: "Use BIOS"
+ // description: "Use official BIOS/bootloader for emulated hardware, if present in RetroArch's system directory."
+ // }
+ ]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/qml/platforms/NesSettings.qml b/qml/platforms/NesSettings.qml
new file mode 100644
index 0000000..d22ae8e
--- /dev/null
+++ b/qml/platforms/NesSettings.qml
@@ -0,0 +1,82 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+FocusScope {
+ id: root
+ Flickable {
+ anchors.fill: parent
+ contentHeight: column.height
+ ColumnLayout {
+ id: column
+ width: parent.width
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 40
+ spacing: 8
+
+ Button {
+ id: backButton
+ background: Rectangle {
+ color: enabled ? (backButton.hovered ? "#404143" : "transparent") : "transparent"
+ radius: height / 2
+
+ }
+
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height
+
+ hoverEnabled: true
+
+ contentItem: Text {
+ text: "\ue5e0"
+ font.family: Constants.symbolFontFamily
+ leftPadding: 8
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ font.pixelSize: 24
+ color: ColorPalette.neutral400
+ }
+
+ checkable: false
+
+ onClicked: {
+ root.StackView.view.pop()
+ }
+ }
+
+ Text {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ text: qsTr("Default NES settings")
+ font.pixelSize: 26
+ font.family: Constants.regularFontFamily
+ font.weight: Font.Bold
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+ color: ColorPalette.neutral100
+ }
+ Layout.bottomMargin: 8
+ }
+
+ Rectangle {
+ Layout.topMargin: 8
+ Layout.bottomMargin: 8
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ Text {
+ Layout.fillWidth: true
+ text: "There's nothing here yet."
+ color: ColorPalette.neutral100
+ font.pixelSize: 15
+ font.family: Constants.regularFontFamily
+ font.weight: Font.DemiBold
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/qml/platforms/Nintendo64Settings.qml b/qml/platforms/Nintendo64Settings.qml
new file mode 100644
index 0000000..da09263
--- /dev/null
+++ b/qml/platforms/Nintendo64Settings.qml
@@ -0,0 +1,82 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+FocusScope {
+ id: root
+ Flickable {
+ anchors.fill: parent
+ contentHeight: column.height
+ ColumnLayout {
+ id: column
+ width: parent.width
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 40
+ spacing: 8
+
+ Button {
+ id: backButton
+ background: Rectangle {
+ color: enabled ? (backButton.hovered ? "#404143" : "transparent") : "transparent"
+ radius: height / 2
+
+ }
+
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height
+
+ hoverEnabled: true
+
+ contentItem: Text {
+ text: "\ue5e0"
+ font.family: Constants.symbolFontFamily
+ leftPadding: 8
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ font.pixelSize: 24
+ color: ColorPalette.neutral400
+ }
+
+ checkable: false
+
+ onClicked: {
+ root.StackView.view.pop()
+ }
+ }
+
+ Text {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ text: qsTr("Default Nintendo 64 settings")
+ font.pixelSize: 26
+ font.family: Constants.regularFontFamily
+ font.weight: Font.Bold
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+ color: ColorPalette.neutral100
+ }
+ Layout.bottomMargin: 8
+ }
+
+ Rectangle {
+ Layout.topMargin: 8
+ Layout.bottomMargin: 8
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ Text {
+ Layout.fillWidth: true
+ text: "There's nothing here yet."
+ color: ColorPalette.neutral100
+ font.pixelSize: 15
+ font.family: Constants.regularFontFamily
+ font.weight: Font.DemiBold
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/qml/platforms/PlatformSettingsPage.qml b/qml/platforms/PlatformSettingsPage.qml
new file mode 100644
index 0000000..1c301ad
--- /dev/null
+++ b/qml/platforms/PlatformSettingsPage.qml
@@ -0,0 +1,303 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+FocusScope {
+ id: root
+
+ Component {
+ id: n64Settings
+ Nintendo64Settings {
+ }
+ }
+
+ Component {
+ id: snesSettings
+ SnesSettings {
+ }
+ }
+
+ Component {
+ id: nesSettings
+ NesSettings {
+ }
+ }
+
+ Component {
+ id: gbaSettings
+ GbaSettings {
+ }
+ }
+
+ Component {
+ id: gbcSettings
+ GameBoyColorSettings {
+ }
+ }
+
+ Component {
+ id: gbSettings
+ GameBoySettings {
+ }
+ }
+
+
+ Flickable {
+ anchors.fill: parent
+ contentHeight: col.height
+ ColumnLayout {
+ spacing: 0
+ id: col
+ width: parent.width
+
+ Text {
+ Layout.fillWidth: true
+ text: qsTr("Platform settings")
+ font.pixelSize: 26
+ font.family: Constants.regularFontFamily
+ font.weight: Font.Bold
+ Layout.bottomMargin: 8
+ color: "white"
+ }
+
+ Text {
+ Layout.fillWidth: true
+ // text: qsTr("Here's where you can change settings for each platform. You can change settings on a per-game basis by selecting the game in your library and going to 'Settings'.")
+ text: qsTr("Here's where you can change the default settings for each platform. Later you'll be able to change them on a per-game basis.")
+ font.pixelSize: 15
+ font.family: Constants.regularFontFamily
+ font.weight: Font.Normal
+ wrapMode: Text.WordWrap
+ color: ColorPalette.neutral300
+ Layout.bottomMargin: 8
+ }
+
+ Rectangle {
+ Layout.topMargin: 8
+ Layout.bottomMargin: 8
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ Button {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 80
+
+ onClicked: function () {
+ root.StackView.view.push(gbSettings)
+ }
+ hoverEnabled: true
+ background: Rectangle {
+ color: parent.hovered ? "#333333" : "transparent"
+ radius: 8
+ }
+ contentItem: Row {
+ spacing: 8
+ Image {
+ source: "file:system/_img/gb.svg"
+ width: parent.height
+ height: parent.height
+ fillMode: Image.PreserveAspectFit
+ sourceSize.height: parent.height
+ }
+ Text {
+ text: qsTr("Game Boy")
+ font.pointSize: 14
+ height: parent.height
+ font.family: Constants.regularFontFamily
+ font.weight: Font.Normal
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignLeft
+ color: "white"
+ }
+
+ }
+
+ }
+ Button {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 80
+
+ onClicked: function () {
+ root.StackView.view.push(gbcSettings)
+ }
+ hoverEnabled: true
+ background: Rectangle {
+ color: parent.hovered ? "#333333" : "transparent"
+ radius: 8
+ }
+ contentItem: Row {
+ spacing: 8
+ Image {
+ source: "file:system/_img/gbc.svg"
+ width: parent.height
+ height: parent.height
+ fillMode: Image.PreserveAspectFit
+ sourceSize.height: parent.height
+ }
+ Text {
+ text: qsTr("Game Boy Color")
+ font.pointSize: 14
+ height: parent.height
+ font.family: Constants.regularFontFamily
+ font.weight: Font.Normal
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignLeft
+ color: "white"
+ }
+
+ }
+
+ }
+ Button {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 80
+
+ onClicked: function () {
+ root.StackView.view.push(gbaSettings)
+ }
+ hoverEnabled: true
+ background: Rectangle {
+ color: parent.hovered ? "#333333" : "transparent"
+ radius: 8
+ }
+ contentItem: Row {
+ spacing: 8
+ Image {
+ source: "file:system/_img/gba.svg"
+ width: parent.height
+ height: parent.height
+ fillMode: Image.PreserveAspectFit
+ sourceSize.height: parent.height
+ }
+ Text {
+ text: qsTr("Game Boy Advance")
+ font.pointSize: 14
+ height: parent.height
+ font.family: Constants.regularFontFamily
+ font.weight: Font.Normal
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignLeft
+ color: "white"
+ }
+
+ }
+
+ }
+ Button {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 80
+
+ onClicked: function () {
+ root.StackView.view.push(nesSettings)
+ }
+ hoverEnabled: true
+ background: Rectangle {
+ color: parent.hovered ? "#333333" : "transparent"
+ radius: 8
+ }
+ contentItem: Row {
+ spacing: 8
+ Image {
+ source: "file:system/_img/nes.svg"
+ width: parent.height
+ height: parent.height
+ fillMode: Image.PreserveAspectFit
+ sourceSize.height: parent.height
+ }
+ Text {
+ text: qsTr("NES")
+ font.pointSize: 14
+ height: parent.height
+ font.family: Constants.regularFontFamily
+ font.weight: Font.Normal
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignLeft
+ color: "white"
+ }
+
+ }
+
+ }
+
+ Button {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 80
+
+ onClicked: function () {
+ root.StackView.view.push(snesSettings)
+ }
+
+ hoverEnabled: true
+ background: Rectangle {
+ color: parent.hovered ? "#333333" : "transparent"
+ radius: 8
+ }
+ contentItem: Row {
+ spacing: 8
+ Image {
+ source: "file:system/_img/SNES.svg"
+ width: parent.height
+ height: parent.height
+ fillMode: Image.PreserveAspectFit
+ sourceSize.height: parent.height
+ }
+ Text {
+ text: qsTr("SNES")
+ height: parent.height
+ font.pointSize: 14
+ font.family: Constants.regularFontFamily
+ font.weight: Font.Normal
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignLeft
+ color: "white"
+ }
+
+ }
+
+ }
+
+ Button {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 80
+
+ onClicked: function () {
+ root.StackView.view.push(n64Settings)
+ }
+ hoverEnabled: true
+ background: Rectangle {
+ color: parent.hovered ? "#333333" : "transparent"
+ radius: 8
+ }
+ contentItem: Row {
+ spacing: 8
+ Image {
+ source: "file:system/_img/N64.svg"
+ width: parent.height
+ height: parent.height
+ fillMode: Image.PreserveAspectFit
+ sourceSize.height: parent.height
+ }
+ Text {
+ text: qsTr("Nintendo 64")
+ height: parent.height
+ font.pointSize: 14
+ font.family: Constants.regularFontFamily
+ font.weight: Font.Normal
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignLeft
+ color: "white"
+ }
+
+ }
+
+ }
+
+ Item {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/qml/platforms/SnesSettings.qml b/qml/platforms/SnesSettings.qml
new file mode 100644
index 0000000..84a1e67
--- /dev/null
+++ b/qml/platforms/SnesSettings.qml
@@ -0,0 +1,82 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+FocusScope {
+ id: root
+ Flickable {
+ anchors.fill: parent
+ contentHeight: column.height
+ ColumnLayout {
+ id: column
+ width: parent.width
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 40
+ spacing: 8
+
+ Button {
+ id: backButton
+ background: Rectangle {
+ color: enabled ? (backButton.hovered ? "#404143" : "transparent") : "transparent"
+ radius: height / 2
+
+ }
+
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height
+
+ hoverEnabled: true
+
+ contentItem: Text {
+ text: "\ue5e0"
+ font.family: Constants.symbolFontFamily
+ leftPadding: 8
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ font.pixelSize: 24
+ color: ColorPalette.neutral400
+ }
+
+ checkable: false
+
+ onClicked: {
+ root.StackView.view.pop()
+ }
+ }
+
+ Text {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ text: qsTr("Default SNES settings")
+ font.pixelSize: 26
+ font.family: Constants.regularFontFamily
+ font.weight: Font.Bold
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+ color: ColorPalette.neutral100
+ }
+ Layout.bottomMargin: 8
+ }
+
+ Rectangle {
+ Layout.topMargin: 8
+ Layout.bottomMargin: 8
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ Text {
+ Layout.fillWidth: true
+ text: "There's nothing here yet."
+ color: ColorPalette.neutral100
+ font.pixelSize: 15
+ font.family: Constants.regularFontFamily
+ font.weight: Font.DemiBold
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/qml/platforms/SnesSettings.qml.bak b/qml/platforms/SnesSettings.qml.bak
new file mode 100644
index 0000000..01a9596
--- /dev/null
+++ b/qml/platforms/SnesSettings.qml.bak
@@ -0,0 +1,1114 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+FocusScope {
+ id: root
+ Flickable {
+ anchors.fill: parent
+ contentHeight: column.height
+ ColumnLayout {
+ id: column
+ width: parent.width
+
+ // Image {
+ // Layout.fillWidth: true
+ // Layout.preferredHeight: parent.width
+ // source: "file:system/_img/snes.svg"
+ // sourceSize.height: height
+ // fillMode: Image.PreserveAspectFit
+ // }
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 40
+ spacing: 8
+
+ Button {
+ id: backButton
+ background: Rectangle {
+ color: enabled ? (backButton.hovered ? "#404143" : "transparent") : "transparent"
+ radius: height / 2
+
+ }
+
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height
+
+ hoverEnabled: true
+
+ contentItem: Text {
+ text: "\ue5e0"
+ font.family: Constants.symbolFontFamily
+ leftPadding: 8
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ font.pixelSize: 24
+ color: ColorPalette.neutral400
+ }
+
+ checkable: false
+
+ onClicked: {
+ console.log(":)")
+ root.StackView.view.pop()
+ }
+ }
+
+ Text {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ text: qsTr("Default SNES Settings")
+ font.pixelSize: 26
+ font.family: Constants.regularFontFamily
+ font.weight: Font.Bold
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+ color: ColorPalette.neutral100
+ }
+ Layout.bottomMargin: 8
+ }
+
+ Rectangle {
+ Layout.topMargin: 8
+ Layout.bottomMargin: 8
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ Text {
+ Layout.fillWidth: true
+ text: qsTr("Advanced settings")
+ font.pixelSize: 14
+ font.family: Constants.regularFontFamily
+ font.weight: Font.DemiBold
+ Layout.bottomMargin: 8
+ color: ColorPalette.neutral400
+ }
+
+ // Option {
+ // Layout.fillWidth: true
+ // Layout.minimumHeight: 42
+ // label: "Console region"
+ // description: "Specify which region the system is from. 'PAL' is 50hz, 'NTSC' is 60hz. Games will run faster or slower than normal if the incorrect region is selected."
+ // control: MyComboBox {
+ // model: [{
+ // text: "Auto",
+ // value: "auto"
+ // }, {
+ // text: "NTSC",
+ // value: "ntsc"
+ // }, {
+ // text: "PAL",
+ // value: "pal"
+ // }]
+ // textRole: "text"
+ // valueRole: "value"
+ //
+ // Component.onCompleted: {
+ // // currentIndex = find(emulator_config_manager.getOptionValueForPlatform(1, "snes9x_region"))
+ // emulator_config_manager.getOptionValueForPlatform(1, "snes9x_region")
+ // }
+ //
+ // onCurrentValueChanged: {
+ // emulator_config_manager.setOptionValueForPlatform(1, "snes9x_region", currentValue)
+ // }
+ // }
+ // }
+
+ // Text {
+ // Layout.fillWidth: true
+ // text: "Console region"
+ // color: ColorPalette.neutral100
+ // font.pixelSize: 15
+ // Layout.alignment: Qt.AlignLeft
+ // font.family: Constants.regularFontFamily
+ // font.weight: Font.DemiBold
+ // verticalAlignment: Text.AlignVCenter
+ // }
+
+ RadioButtonGroup {
+ Layout.fillWidth: true
+
+ label: "Console region"
+ // description: "Specify which region the system is from. 'PAL' is 50hz, 'NTSC' is 60hz. Games will run faster or slower than normal if the incorrect region is selected."
+
+ model: [{
+ label: "Auto",
+ description: "Chooses NTSC or PAL based on the game when it is loaded."
+ }, {
+ label: "NTSC",
+ description: "Runs games at 60hz."
+ }, {
+ label: "PAL",
+ description: "Run games at 50hz"
+ }]
+ }
+
+ Text {
+ Layout.topMargin: 30
+ Layout.fillWidth: true
+ text: qsTr("Stuff")
+ font.pixelSize: 14
+ font.family: Constants.regularFontFamily
+ font.weight: Font.DemiBold
+ Layout.bottomMargin: 4
+ color: "#a6a6a6"
+ }
+
+ Pane {
+ Layout.fillWidth: true
+ verticalPadding: overclockCol.stuffCol
+ background: Rectangle {
+ radius: 8
+ color: ColorPalette.neutral800
+ }
+
+ contentItem: ColumnLayout {
+ id: stuffCol
+
+ ToggleOption {
+ Layout.fillWidth: true
+ label: "Allow opposing D-Pad directions"
+ description: "Enabling this will allow pressing / quickly alternating / holding both left and right (or up and down) directions at the same time. This may cause movement-based glitches."
+
+ Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled"
+ emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed")
+ }
+
+ onCheckedChanged: function () {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_up_down_allowed", "enabled")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_up_down_allowed", "disabled")
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ ToggleOption {
+ Layout.fillWidth: true
+ label: "Crop overscan"
+ description: "Remove the borders at the top and bottom of the screen, typically unused by games and hidden by the bezel of a standard-definition television. 'Auto' will attempt to detect and crop the ~8 pixel overscan based on the current content."
+
+ Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled"
+ emulator_config_manager.getOptionValueForPlatform(1, "snes9x_overscan")
+ }
+
+ onCheckedChanged: function () {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_overscan", "enabled")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_overscan", "disabled")
+ }
+ }
+
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ ToggleOption {
+ Layout.fillWidth: true
+ label: "Enable graphic clip windows"
+
+ Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled"
+ emulator_config_manager.getOptionValueForPlatform(1, "snes9x_gfx_clip")
+ }
+
+ onCheckedChanged: function () {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_gfx_clip", "enabled")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_gfx_clip", "disabled")
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ ToggleOption {
+ Layout.fillWidth: true
+ label: "Enable transparency effects"
+
+ Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled"
+ emulator_config_manager.getOptionValueForPlatform(1, "snes9x_gfx_transp")
+ }
+
+ onCheckedChanged: function () {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_gfx_transp", "enabled")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_gfx_transp", "disabled")
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ ToggleOption {
+ Layout.fillWidth: true
+ label: "Hi-Res blending"
+ description: "Blend adjacent pixels when game switches to hi-res mode (512x448). Required for certain games that use hi-res mode to produce transparency effects (Kirby's Dream Land, Jurassic Park...)."
+
+
+ Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled"
+ emulator_config_manager.getOptionValueForPlatform(1, "snes9x_hires_blend")
+ }
+
+ onCheckedChanged: function () {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_hires_blend", "blur")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_hires_blend", "disabled")
+ }
+ }
+ }
+ }
+ }
+
+ Text {
+ Layout.topMargin: 30
+ Layout.fillWidth: true
+ text: qsTr("Overclocking")
+ font.pixelSize: 14
+ font.family: Constants.regularFontFamily
+ font.weight: Font.DemiBold
+ Layout.bottomMargin: 4
+ color: "#a6a6a6"
+ }
+
+ Pane {
+ Layout.fillWidth: true
+ verticalPadding: overclockCol.spacing
+ background: Rectangle {
+ radius: 8
+ color: ColorPalette.neutral800
+ }
+
+ contentItem: ColumnLayout {
+ id: overclockCol
+ Option {
+ Layout.fillWidth: true
+ label: "SuperFX overclock"
+ description: "SuperFX coprocessor frequency multiplier. Can improve frame rate or cause timing errors. Values under 100% can improve game performance on slow devices."
+
+ control: MyComboBox {
+ model: [{
+ text: "50%",
+ value: "50%"
+ }, {
+ text: "60%",
+ value: "60%"
+ }, {
+ text: "70%",
+ value: "70%"
+ }, {
+ text: "80%",
+ value: "80%"
+ }, {
+ text: "90%",
+ value: "90%"
+ }, {
+ text: "100%",
+ value: "100%"
+ }, {
+ text: "150%",
+ value: "150%"
+ }, {
+ text: "200%",
+ value: "200%"
+ }, {
+ text: "250%",
+ value: "250%"
+ }, {
+ text: "300%",
+ value: "300%"
+ }, {
+ text: "350%",
+ value: "350%"
+ }, {
+ text: "400%",
+ value: "400%"
+ }, {
+ text: "450%",
+ value: "450%"
+ }, {
+ text: "500%",
+ value: "500%"
+ }]
+
+ currentIndex: 5
+ textRole: "text"
+ valueRole: "value"
+
+ Component.onCompleted: {
+ // currentIndex = find(emulator_config_manager.getOptionValueForPlatform(1, "snes9x_region"))
+ emulator_config_manager.getOptionValueForPlatform(1, "snes9x_overclock_superfx")
+ }
+
+ onCurrentValueChanged: {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_overclock_superfx", currentValue)
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ Option {
+ Layout.fillWidth: true
+ Layout.minimumHeight: 42
+ label: "CPU overclock"
+ description: "Overclock SNES CPU. May cause games to crash! Use 'Light' for shorter loading times, 'Compatible' for most games exhibiting slowdown and 'Max' only if absolutely required (Gradius 3, Super R-type...)."
+ // control: MyComboBox {
+ // model: [{
+ // text: "None",
+ // value: "disabled"
+ // }, {
+ // text: "Light",
+ // value: "light"
+ // }, {
+ // text: "Compatible",
+ // value: "compatible"
+ // }, {
+ // text: "Max",
+ // value: "max"
+ // }]
+ // textRole: "text"
+ // valueRole: "value"
+ //
+ // Component.onCompleted: {
+ // // currentIndex = find(emulator_config_manager.getOptionValueForPlatform(1, "snes9x_region"))
+ // emulator_config_manager.getOptionValueForPlatform(1, "snes9x_overclock_cycles")
+ // }
+ //
+ // onCurrentValueChanged: {
+ // emulator_config_manager.setOptionValueForPlatform(1, "snes9x_overclock_cycles", currentValue)
+ // }
+ // }
+ }
+
+ Slider {
+ id: slider
+ Layout.fillWidth: true
+ Layout.preferredHeight: 48
+ from: 0
+ to: 3
+ stepSize: 1
+ snapMode: Slider.SnapAlways
+
+ handle: Rectangle {
+ color: "white"
+ width: 10
+ height: 24
+ radius: 2
+ x: slider.width * slider.visualPosition - (width / 2)
+ y: ((slider.height * 2 / 3) / 2) - (height / 2)
+ z: 4
+ }
+
+ background: Item {
+ Item {
+ height: parent.height * 2 / 3
+ width: parent.width
+ Rectangle {
+ height: 8
+ width: parent.width
+ y: parent.height / 2 - (height / 2)
+ color: "grey"
+ Rectangle {
+ height: 24
+ color: "grey"
+ width: 2
+ x: -(width / 2)
+ y: -height / 2 + (parent.height / 2)
+ }
+
+ Rectangle {
+ height: 24
+ color: "grey"
+ width: 2
+ x: slider.width / 3 - (width / 2)
+ y: -height / 2 + (parent.height / 2)
+ }
+
+ Rectangle {
+ height: 24
+ width: 2
+ color: "grey"
+ x: slider.width * 2 / 3 - (width / 2)
+ y: -height / 2 + (parent.height / 2)
+ }
+
+ Rectangle {
+ height: 24
+ width: 2
+ color: "grey"
+ x: slider.width - (width / 2)
+ y: -height / 2 + (parent.height / 2)
+ }
+
+ Rectangle {
+ color: "lightblue"
+ height: parent.height
+ width: slider.width * slider.visualPosition
+ }
+ }
+ }
+ Item {
+ width: parent.width
+ height: parent.height / 3
+ y: parent.height * 2 / 3
+
+ Text {
+ text: "None"
+ font.pointSize: 8
+ color: "white"
+ x: -(width / 2)
+ }
+
+ Text {
+ text: "Light"
+ font.pointSize: 8
+ color: "white"
+ x: slider.width / 3 - (width / 2)
+ }
+
+ Text {
+ text: "Compatible"
+ font.pointSize: 8
+ color: "white"
+ x: slider.width * 2 / 3 - (width / 2)
+ }
+
+ Text {
+ text: "Max"
+ font.pointSize: 8
+ color: "white"
+ x: slider.width - (width / 2)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Text {
+ Layout.topMargin: 30
+ Layout.fillWidth: true
+ text: qsTr("Emulation hacks")
+ font.pixelSize: 14
+ font.family: Constants.regularFontFamily
+ font.weight: Font.DemiBold
+ Layout.bottomMargin: 4
+ color: ColorPalette.neutral400
+ }
+
+ Pane {
+ Layout.fillWidth: true
+ verticalPadding: hackCol.spacing
+ background: Rectangle {
+ radius: 8
+ color: ColorPalette.neutral800
+ }
+
+ contentItem: ColumnLayout {
+ id: hackCol
+ ToggleOption {
+ Layout.fillWidth: true
+ label: "Increase sprite limit to reduce flickering"
+
+
+ Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled"
+ emulator_config_manager.getOptionValueForPlatform(1, "snes9x_reduce_sprite_flicker")
+ }
+
+ onCheckedChanged: function () {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_reduce_sprite_flicker", "enabled")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_reduce_sprite_flicker", "disabled")
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ ToggleOption {
+ Layout.fillWidth: true
+ label: "Randomize system RAM at startup"
+
+ Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled"
+ emulator_config_manager.getOptionValueForPlatform(1, "snes9x_randomize_memory")
+ }
+
+ onCheckedChanged: function () {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_randomize_memory", "enabled")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_randomize_memory", "disabled")
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ ToggleOption {
+ Layout.fillWidth: true
+ Layout.minimumHeight: 42
+ label: "Block invalid VRAM access"
+ description: "Some homebrew/ROM hacks require this option to be disabled for correct operation."
+
+ Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled"
+ emulator_config_manager.getOptionValueForPlatform(1, "snes9x_block_invalid_vram_access")
+ }
+
+ onCheckedChanged: function () {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_block_invalid_vram_access", "enabled")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_block_invalid_vram_access", "disabled")
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ ToggleOption {
+ Layout.fillWidth: true
+ Layout.minimumHeight: 42
+ label: "Echo buffer hack"
+ description: "Some homebrew/ROM hacks require this option to be enabled for correct operation."
+ Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled"
+ emulator_config_manager.getOptionValueForPlatform(1, "snes9x_echo_buffer_hack")
+ }
+
+ onCheckedChanged: function () {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_echo_buffer_hack", "enabled")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_echo_buffer_hack", "disabled")
+ }
+ }
+ }
+ }
+ }
+
+ Text {
+ Layout.topMargin: 30
+ Layout.fillWidth: true
+ text: qsTr("Graphical layers")
+ font.pixelSize: 14
+ font.family: Constants.regularFontFamily
+ font.weight: Font.DemiBold
+ Layout.bottomMargin: 4
+ color: ColorPalette.neutral400
+ }
+
+ Pane {
+ Layout.fillWidth: true
+ verticalPadding: layerCol.spacing
+ background: Rectangle {
+ radius: 8
+ color: ColorPalette.neutral800
+ }
+
+ contentItem: ColumnLayout {
+ id: layerCol
+ ToggleOption {
+ Layout.fillWidth: true
+ Layout.minimumHeight: 20
+ label: "Show layer 1"
+
+ Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled"
+ emulator_config_manager.getOptionValueForPlatform(1, "snes9x_layer_1")
+ }
+
+ onCheckedChanged: function () {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_layer_1", "enabled")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_layer_1", "disabled")
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ ToggleOption {
+ Layout.fillWidth: true
+ Layout.minimumHeight: 20
+ label: "Show layer 2"
+
+ Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled"
+ emulator_config_manager.getOptionValueForPlatform(1, "snes9x_layer_2")
+ }
+
+ onCheckedChanged: function () {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_layer_2", "enabled")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_layer_2", "disabled")
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ ToggleOption {
+ Layout.fillWidth: true
+ Layout.minimumHeight: 20
+ label: "Show layer 3"
+
+ Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled"
+ emulator_config_manager.getOptionValueForPlatform(1, "snes9x_layer_3")
+ }
+
+ onCheckedChanged: function () {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_layer_3", "enabled")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_layer_3", "disabled")
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ ToggleOption {
+ Layout.fillWidth: true
+ Layout.minimumHeight: 20
+ label: "Show layer 4"
+
+ Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled"
+ emulator_config_manager.getOptionValueForPlatform(1, "snes9x_layer_4")
+ }
+
+ onCheckedChanged: function () {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_layer_4", "enabled")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_layer_4", "disabled")
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ ToggleOption {
+ Layout.fillWidth: true
+ Layout.minimumHeight: 20
+ label: "Show layer 5 (sprite layer)"
+
+ Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled"
+ emulator_config_manager.getOptionValueForPlatform(1, "snes9x_layer_5")
+ }
+
+ onCheckedChanged: function () {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_layer_5", "enabled")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_layer_5", "disabled")
+ }
+ }
+ }
+ }
+ }
+
+ Text {
+ Layout.topMargin: 30
+ Layout.fillWidth: true
+ text: qsTr("Sound channels")
+ font.pixelSize: 14
+ font.family: Constants.regularFontFamily
+ font.weight: Font.DemiBold
+ Layout.bottomMargin: 4
+ color: ColorPalette.neutral400
+ }
+
+ Pane {
+ Layout.fillWidth: true
+ verticalPadding: soundCol.spacing
+ background: Rectangle {
+ radius: 8
+ color: ColorPalette.neutral800
+ }
+
+ contentItem: ColumnLayout {
+ id: soundCol
+ ToggleOption {
+ Layout.fillWidth: true
+ Layout.minimumHeight: 24
+ label: "Enable channel 1"
+
+ Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled"
+ emulator_config_manager.getOptionValueForPlatform(1, "snes9x_sndchan_1")
+ }
+
+ onCheckedChanged: function () {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_1", "enabled")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_1", "disabled")
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ ToggleOption {
+ Layout.fillWidth: true
+ Layout.minimumHeight: 24
+ label: "Enable channel 2"
+
+
+ Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled"
+ emulator_config_manager.getOptionValueForPlatform(1, "snes9x_sndchan_2")
+ }
+
+ onCheckedChanged: function () {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_2", "enabled")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_2", "disabled")
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ ToggleOption {
+ Layout.fillWidth: true
+ Layout.minimumHeight: 24
+ label: "Enable channel 3"
+
+ Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled"
+ emulator_config_manager.getOptionValueForPlatform(1, "snes9x_sndchan_3")
+ }
+
+ onCheckedChanged: function () {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_3", "enabled")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_3", "disabled")
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ ToggleOption {
+ Layout.fillWidth: true
+ Layout.minimumHeight: 24
+ label: "Enable channel 4"
+
+ Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled"
+ emulator_config_manager.getOptionValueForPlatform(1, "snes9x_sndchan_4")
+ }
+
+ onCheckedChanged: function () {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_4", "enabled")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_4", "disabled")
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ ToggleOption {
+ Layout.fillWidth: true
+ Layout.minimumHeight: 24
+ label: "Enable channel 5"
+
+ Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled"
+ emulator_config_manager.getOptionValueForPlatform(1, "snes9x_sndchan_5")
+ }
+
+ onCheckedChanged: function () {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_5", "enabled")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_5", "disabled")
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ ToggleOption {
+ Layout.fillWidth: true
+ Layout.minimumHeight: 24
+ label: "Enable channel 6"
+
+ Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled"
+ emulator_config_manager.getOptionValueForPlatform(1, "snes9x_sndchan_6")
+ }
+
+ onCheckedChanged: function () {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_6", "enabled")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_6", "disabled")
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ ToggleOption {
+ Layout.fillWidth: true
+ Layout.minimumHeight: 24
+ label: "Enable channel 7"
+
+ Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled"
+ emulator_config_manager.getOptionValueForPlatform(1, "snes9x_sndchan_7")
+ }
+
+ onCheckedChanged: function () {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_7", "enabled")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_7", "disabled")
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ ToggleOption {
+ Layout.fillWidth: true
+ Layout.minimumHeight: 24
+ label: "Enable channel 8"
+
+ Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled"
+ emulator_config_manager.getOptionValueForPlatform(1, "snes9x_sndchan_8")
+ }
+
+ onCheckedChanged: function () {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_8", "enabled")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_8", "disabled")
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: "#333333"
+ }
+
+ ToggleOption {
+ Layout.fillWidth: true
+ Layout.minimumHeight: 24
+ label: "Enable channel 9"
+
+ Component.onCompleted: {
+ // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled"
+ emulator_config_manager.getOptionValueForPlatform(1, "snes9x_sndchan_9")
+ }
+
+ onCheckedChanged: function () {
+ if (checked) {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_9", "enabled")
+ } else {
+ emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_9", "disabled")
+ }
+ }
+ }
+ }
+ }
+
+ // TODO: Advanced option
+ // ToggleOption {
+ // Layout.fillWidth: true
+ // label: "Allow Opposing Directions"
+ // description: "Allow pressing (D-Pad Up + D-Pad Down) or (D-Pad Left + D-Pad Right) simultaneously. This isn't allowed on original hardware, so it may cause issues in some games."
+ //
+ // checked: achievement_manager.unlockNotificationsEnabled
+ //
+ // onCheckedChanged: {
+ // achievement_manager.unlockNotificationsEnabled = checked
+ // }
+ // }
+
+ // ToggleOption {
+ // Layout.fillWidth: true
+ // label: "Play sound"
+ // Layout.leftMargin: 24
+ // }
+
+ // Rectangle {
+ // Layout.fillWidth: true
+ // Layout.preferredHeight: 1
+ // color: "#333333"
+ // }
+
+ // ToggleOption {
+ // id: colorCorrectionOption
+ // Layout.fillWidth: true
+ // Layout.minimumHeight: 42
+ // label: "Color correction"
+ // description: "Make the screen colors more accurate to the original hardware."
+ // }
+ //
+ // ComboBoxOption {
+ // Layout.fillWidth: true
+ // Layout.minimumHeight: 42
+ // Layout.leftMargin: 24
+ // visible: colorCorrectionOption.checked
+ // label: "Frontlight position"
+ // description: "Simulates the physical response of the Game Boy Color LCD panel when illuminated from different angles."
+ // }
+ //
+ // Rectangle {
+ // Layout.fillWidth: true
+ // Layout.preferredHeight: 1
+ // color: "#333333"
+ // }
+ //
+ // SliderOption {
+ // Layout.fillWidth: true
+ // Layout.minimumHeight: 42
+ // label: "Dark filter level"
+ // description: "Darken the screen to reduce glare and/or eye strain. This is useful for games with white backgrounds, as they appear harsher than intended on modern displays."
+ // }
+ //
+ // Rectangle {
+ // Layout.fillWidth: true
+ // Layout.preferredHeight: 1
+ // color: "#333333"
+ // }
+ //
+ // ToggleOption {
+ // Layout.fillWidth: true
+ // Layout.minimumHeight: 42
+ // label: "Simulate LCD ghosting"
+ // description: "Enables simulation of LCD ghosting effects by blending the current and previous frames."
+ //
+ // onCheckedChanged: {
+ // if (checked) {
+ // emulator_config_manager.setOptionValueForPlatform(1, "gambatte_gb_colorization", "auto")
+ // } else {
+ // emulator_config_manager.setOptionValueForPlatform(1, "gambatte_gb_colorization", "disabled")
+ // }
+ // }
+ //
+ // }
+ //
+ // Rectangle {
+ // Layout.fillWidth: true
+ // Layout.preferredHeight: 1
+ // color: "#333333"
+ // }
+
+ Item {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/qml/screens/EmulatorScreen.qml b/qml/screens/EmulatorScreen.qml
new file mode 100644
index 0000000..c5744b0
--- /dev/null
+++ b/qml/screens/EmulatorScreen.qml
@@ -0,0 +1,393 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Dialogs
+import QtQuick.Window
+import QtQuick.Layouts 1.0
+import QtQuick.Effects
+import Firelight 1.0
+
+FocusScope {
+ id: root
+
+ property bool emulatorIsRunning: emulatorStack.depth > 0
+
+ signal gameReady()
+
+ signal gameAboutToStop()
+
+ signal gameStopped()
+
+ function loadGame(id) {
+ emulatorStack.pushItem(emulatorComponent, {entryId: id}, StackView.Immediate)
+ // emulator.loadGame(id)
+ }
+
+ Rectangle {
+ id: background
+ color: "black"
+ anchors.fill: parent
+ visible: root.emulatorIsRunning
+ }
+
+ StackView {
+ id: emulatorStack
+ anchors.fill: parent
+ focus: true
+
+ property bool suspended: false
+ property bool running: false
+
+ // Keys.onEscapePressed: function (event) {
+ // if (event.isAutoRepeat) {
+ // return
+ // }
+ //
+ // if (emulatorStack.depth === 1) {
+ // emulatorStack.pushItem(nowPlayingPage, {}, StackView.PushTransition)
+ // } else if (emulatorStack.depth === 2) {
+ // emulatorStack.pop()
+ // }
+ // }
+
+ pushEnter: Transition {
+ ParallelAnimation {
+ PropertyAnimation {
+ property: "opacity"
+ from: 0
+ to: 1
+ duration: 250
+ easing.type: Easing.InOutQuad
+ }
+ PropertyAnimation {
+ property: "x"
+ from: -20
+ to: 0
+ duration: 250
+ easing.type: Easing.InOutQuad
+ }
+ }
+ }
+ pushExit: Transition {
+
+ }
+ popEnter: Transition {
+ }
+ popExit: Transition {
+ ParallelAnimation {
+ PropertyAnimation {
+ property: "opacity"
+ from: 1
+ to: 0
+ duration: 250
+ easing.type: Easing.InOutQuad
+ }
+ PropertyAnimation {
+ property: "x"
+ from: 0
+ to: -20
+ duration: 250
+ easing.type: Easing.InOutQuad
+ }
+ }
+ }
+ replaceEnter: Transition {
+ }
+ replaceExit: Transition {
+ }
+ }
+
+ Component {
+ id: emulatorComponent
+
+ EmulatorPage {
+ id: emulator
+
+ property bool loaded: false
+
+ required property int entryId
+
+ Component.onCompleted: {
+ emulator.loadGame(entryId)
+ }
+
+ onEmulationStarted: function () {
+ emulator.loaded = true
+ }
+
+ ChallengeIndicatorList {
+ id: challengeIndicators
+ visible: achievement_manager.challengeIndicatorsEnabled
+
+ anchors.top: parent.top
+ anchors.right: parent.right
+ anchors.topMargin: 16
+ anchors.rightMargin: 16
+ height: 100
+ width: 300
+ }
+
+ Keys.onEscapePressed: function (event) {
+ if (event.isAutoRepeat || !emulator.loaded) {
+ return
+ }
+
+ if (emulator.StackView.status === StackView.Active) {
+ emulatorStack.pushItem(nowPlayingPage, {}, StackView.PushTransition)
+ }
+ }
+
+ states: [
+ State {
+ name: "stopped"
+ },
+ State {
+ name: "suspended"
+ PropertyChanges {
+ target: emulatorDimmer
+ opacity: 0.4
+ }
+ PropertyChanges {
+ emulator {
+ layer.enabled: true
+ blurAmount: 1
+ }
+ }
+ },
+ State {
+ name: "running"
+ PropertyChanges {
+ target: emulatorDimmer
+ opacity: 0
+ }
+ PropertyChanges {
+ emulator {
+ layer.enabled: false
+ blurAmount: 0
+ }
+ }
+ }
+ ]
+
+ transitions: [
+ Transition {
+ from: "*"
+ to: "suspended"
+ SequentialAnimation {
+ ScriptAction {
+ script: {
+ emulator.pauseGame()
+ }
+ }
+ PropertyAction {
+ target: emulator
+ property: "layer.enabled"
+ value: true
+ }
+ ParallelAnimation {
+ NumberAnimation {
+ properties: "blurAmount"
+ duration: 250
+ easing.type: Easing.InOutQuad
+ }
+ NumberAnimation {
+ target: emulatorDimmer
+ properties: "opacity"
+ duration: 250
+ easing.type: Easing.InOutQuad
+ }
+ }
+ }
+ },
+ Transition {
+ from: "*"
+ to: "running"
+ SequentialAnimation {
+ ParallelAnimation {
+ NumberAnimation {
+ properties: "blurAmount"
+ duration: 250
+ easing.type: Easing.InOutQuad
+ }
+ NumberAnimation {
+ target: emulatorDimmer
+ properties: "opacity"
+ duration: 250
+ easing.type: Easing.InOutQuad
+ }
+ }
+ PropertyAction {
+ target: emulator
+ property: "layer.enabled"
+ value: false
+ }
+
+ ScriptAction {
+ script: {
+ emulator.resumeGame()
+ }
+
+ }
+ }
+ }
+ ]
+
+ StackView.visible: true
+
+ StackView.onActivating: {
+ state = "running"
+ }
+
+ StackView.onDeactivating: {
+ state = "suspended"
+ }
+
+ property double blurAmount: 0
+
+ Behavior on blurAmount {
+ NumberAnimation {
+ duration: 250
+ easing.type: Easing.InOutQuad
+ }
+ }
+
+ layer.enabled: false
+ layer.effect: MultiEffect {
+ source: emulator
+ anchors.fill: emulator
+ blurEnabled: true
+ blurMultiplier: 1.0
+ blurMax: 64
+ blur: emulator.blurAmount
+ }
+
+ Rectangle {
+ id: emulatorDimmer
+ anchors.fill: parent
+ color: "black"
+ opacity: 0
+
+ Behavior on opacity {
+ NumberAnimation {
+ duration: 250
+ easing.type: Easing.InOutQuad
+ }
+ }
+ }
+
+ Connections {
+ target: window_resize_handler
+
+ function onWindowResizeStarted() {
+ if (emulator.StackView.status === StackView.Active) {
+ emulator.pauseGame()
+ }
+ }
+
+ function onWindowResizeFinished() {
+ if (emulator.StackView.status === StackView.Active) {
+ emulator.resumeGame()
+ }
+ }
+ }
+ }
+ }
+
+ SequentialAnimation {
+ id: closeGameAnimation
+
+ ScriptAction {
+ script: {
+ root.gameAboutToStop()
+ }
+ }
+
+ PauseAnimation {
+ duration: 500
+ }
+
+ ScriptAction {
+ script: {
+ emulatorStack.clear()
+ }
+ }
+
+ ScriptAction {
+ script: {
+ root.gameStopped()
+ }
+ }
+
+
+ }
+
+
+ Component {
+ id: nowPlayingPage
+ NowPlayingPage {
+ id: me
+ property bool topLevel: true
+ property string topLevelName: "nowPlaying"
+
+ Keys.onEscapePressed: function (event) {
+ if (event.isAutoRepeat) {
+ return
+ }
+
+ if (me.StackView.status === StackView.Active) {
+ // emulatorStack.pop()
+ me.StackView.view.popCurrentItem()
+ }
+ }
+
+ onBackToMainMenuPressed: function () {
+ // root.StackView.view.popCurrentItem()
+ // stackView.push(homeScreen)
+ }
+
+ onResumeGamePressed: function () {
+ emulatorStack.pop()
+ }
+
+ onRestartGamePressed: function () {
+ const emu = emulatorStack.get(0)
+ emu.resetGame()
+ // emulator.resetGame()
+ emulatorStack.pop()
+ }
+
+ onCloseGamePressed: function () {
+ closeGameAnimation.running = true
+ // emulatorStack.popCurrentItem()
+ // closeGameAnimation.start()
+ }
+ }
+ }
+
+ AchievementProgressIndicator {
+ id: achievementProgressIndicator
+
+ Connections {
+ target: achievement_manager
+
+ function onAchievementProgressUpdated(imageUrl, id, name, description, current, desired) {
+ if (achievement_manager.progressNotificationsEnabled) {
+ achievementProgressIndicator.openWith(imageUrl, name, description, current, desired)
+ }
+ }
+ }
+ }
+
+ AchievementUnlockIndicator {
+ id: achievementUnlockIndicator
+
+ Connections {
+ target: achievement_manager
+
+ function onAchievementUnlocked(imageUrl, name, description) {
+ if (achievement_manager.unlockNotificationsEnabled) {
+ achievementUnlockIndicator.openWith(imageUrl, name, description)
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/qml/screens/HomeScreen.qml b/qml/screens/HomeScreen.qml
new file mode 100644
index 0000000..2b8b76c
--- /dev/null
+++ b/qml/screens/HomeScreen.qml
@@ -0,0 +1,368 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Dialogs
+import QtQuick.Window
+import QtQuick.Layouts 1.0
+import Firelight 1.0
+
+FocusScope {
+ id: root
+
+ signal startGame(entryId: int)
+
+ property bool showNowPlayingButton: false
+
+ Keys.onEscapePressed: function (event) {
+ if (root.StackView.status !== StackView.Active && !event.isAutoRepeat) {
+ closeAppConfirmationDialog.open()
+ }
+ }
+
+ Component {
+ id: modsPage
+ DiscoverPage {
+ property bool topLevel: true
+ property string topLevelName: "mods"
+ }
+ }
+
+ Component {
+ id: libraryPage2
+ LibraryPage2 {
+ objectName: "Library Page 2"
+ property bool topLevel: true
+ property string topLevelName: "library"
+ model: library_short_model
+
+ onOpenDetails: function (id) {
+ contentStack.push(gameDetailsPage)
+ }
+
+ Component.onCompleted: {
+ startGame.connect(root.startGame)
+ }
+
+ Component {
+ id: gameDetailsPage
+ GameDetailsPage {
+ entryId: 115
+ }
+ }
+
+ // onEntryClicked: function (id) {
+ // emulatorScreen.loadGame(id)
+ // }
+ }
+ }
+
+
+ Component {
+ id: controllerPage
+ ControllersPage {
+ property bool topLevel: true
+ property string topLevelName: "controllers"
+ }
+ }
+
+ Pane {
+ id: drawer
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ width: 200
+ background: Rectangle {
+ color: "#101114"
+ }
+ padding: 4
+ focus: true
+
+ ColumnLayout {
+ anchors.fill: parent
+ spacing: 0
+
+ Item {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 10
+ }
+
+ Text {
+ text: "Firelight"
+ opacity: parent.width > 48 ? 1 : 0
+ color: "#dadada"
+ font.pointSize: 12
+ font.weight: Font.DemiBold
+ font.family: Constants.regularFontFamily
+ Layout.fillWidth: true
+ horizontalAlignment: Text.AlignHCenter
+ }
+
+ Text {
+ text: "alpha (0.5.0a)"
+ opacity: parent.width > 48 ? 1 : 0
+ color: "#dadada"
+ font.pointSize: 8
+ font.weight: Font.DemiBold
+ font.family: Constants.regularFontFamily
+ Layout.fillWidth: true
+ horizontalAlignment: Text.AlignHCenter
+ }
+
+ Text {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 12
+ }
+ NavMenuButton {
+ id: homeNavButton
+ KeyNavigation.down: libraryNavButton
+ labelText: "Dashboard"
+ labelIcon: "\ue871"
+ Layout.preferredWidth: parent.width
+ Layout.preferredHeight: 48
+ enabled: false
+
+
+ checked: contentStack.currentPageName === "home"
+ // checked: stackview.topLevelName === "home"
+ }
+ NavMenuButton {
+ id: modNavButton
+ KeyNavigation.down: controllersNavButton
+ labelText: "Mod Shop"
+ labelIcon: "\uef48"
+ Layout.preferredWidth: parent.width
+ Layout.preferredHeight: 48
+ enabled: false
+ checked: contentStack.currentPageName === "mods"
+
+ // checked: stackview.topLevelName === "mods"
+
+ onToggled: function () {
+ // stackview.replace(null, modsPage)
+ contentStack.goTo(modsPage)
+ }
+ }
+ NavMenuButton {
+ id: libraryNavButton
+ KeyNavigation.down: modNavButton
+ labelText: "My Library"
+ labelIcon: "\uf53e"
+ Layout.preferredWidth: parent.width
+ Layout.preferredHeight: 48
+ focus: true
+
+ // checked: stackview.topLevelName === "library"
+ checked: contentStack.currentPageName === "library"
+
+ onToggled: function () {
+ // stackview.replace(null, libraryPage)
+ contentStack.goTo(libraryPage2)
+ }
+ }
+ NavMenuButton {
+ id: controllersNavButton
+ // KeyNavigation.down: nowPlayingNavButton
+ labelText: "Controllers"
+ labelIcon: "\uf135"
+ Layout.preferredWidth: parent.width
+ Layout.preferredHeight: 48
+
+ // enabled: true
+
+ // checked: stackview.topLevelName === "controllers"
+ checked: contentStack.currentPageName === "controllers"
+
+ onToggled: function () {
+ contentStack.goTo(controllerPage)
+ // stackview.replace(null, controllerPage)
+ }
+ }
+ Item {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ }
+ NavMenuButton {
+ id: nowPlayingNavButton
+ labelText: "Back to game"
+ labelIcon: "\ue037"
+ Layout.preferredWidth: parent.width
+ Layout.preferredHeight: 48
+
+ visible: root.showNowPlayingButton
+ // visible: emulator.running
+
+ checkable: false
+
+ onClicked: function () {
+ root.StackView.view.pop()
+ }
+ }
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.topMargin: 8
+ Layout.bottomMargin: 4
+ Layout.preferredHeight: 1
+ color: "#404143"
+ }
+
+ RowLayout {
+ Layout.preferredWidth: parent.width
+ Layout.preferredHeight: 48
+ Layout.maximumHeight: 48
+ spacing: 8
+
+ Button {
+ background: Rectangle {
+ color: "transparent"
+ radius: 4
+ }
+ Layout.preferredHeight: 42
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
+
+ checkable: false
+ }
+
+ Button {
+ id: me
+ background: Rectangle {
+ color: enabled ? (me.hovered ? "#404143" : "transparent") : "transparent"
+ radius: 4
+ }
+ Layout.preferredHeight: 42
+ Layout.preferredWidth: 42
+ Layout.alignment: Qt.AlignCenter
+
+ hoverEnabled: true
+
+ contentItem: Text {
+ text: "\ue8b8"
+ font.family: Constants.symbolFontFamily
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ font.pixelSize: 26
+ color: "#c3c3c3"
+ }
+
+ checkable: false
+
+ onClicked: {
+ Router.navigateTo("settings")
+ }
+ }
+ }
+
+
+ }
+ }
+
+ StackView {
+ id: contentStack
+ anchors.top: parent.top
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ anchors.left: drawer.right
+ KeyNavigation.left: drawer
+
+ //
+ objectName: "Home Content Stack View"
+ // anchors.fill: parent
+
+ property alias currentPageName: contentStack.topLevelName
+
+ function goTo(page) {
+ contentStack.replace(null, page)
+ }
+
+ property string topLevelName: ""
+
+ onCurrentItemChanged: {
+ if (currentItem) {
+ let top = contentStack.find(function (item, index) {
+ return item.topLevel === true
+ })
+
+ contentStack.topLevelName = top ? top.topLevelName : ""
+ }
+ }
+
+ // Pane {
+ // width: 48
+ // height: 48
+ //
+ // z: 2
+ //
+ // background: Item {
+ // }
+ //
+ // Button {
+ // id: melol
+ // anchors.left: parent.left
+ // anchors.verticalCenter: parent.verticalCenter
+ // // horizontalPadding: 12
+ // // verticalPadding: 8
+ //
+ // enabled: stackview.depth > 1
+ //
+ // hoverEnabled: false
+ //
+ // HoverHandler {
+ // id: myHover
+ // cursorShape: melol.enabled ? Qt.PointingHandCursor : Qt.ForbiddenCursor
+ // }
+ //
+ // background: Rectangle {
+ // color: enabled ? myHover.hovered ? "#4e535b" : "#3e434b" : "#3e434b"
+ // radius: height / 2
+ // }
+ //
+ // contentItem: Text {
+ // text: "\ue5c4"
+ // color: enabled ? "white" : "#7d848c"
+ // font.pointSize: 11
+ // font.family: Constants.symbolFontFamily
+ // horizontalAlignment: Text.AlignHCenter
+ // verticalAlignment: Text.AlignVCenter
+ // }
+ //
+ // onClicked: {
+ // stackview.pop()
+ // }
+ // }
+ // }
+
+ initialItem: libraryPage2
+
+ pushEnter: Transition {
+ }
+ pushExit: Transition {
+ }
+ popEnter: Transition {
+ }
+ popExit: Transition {
+ }
+ replaceEnter: Transition {
+ }
+ replaceExit: Transition {
+ }
+ }
+
+ // HomeContentPane {
+ // id: homeContentPane
+ // anchors.top: parent.top
+ // anchors.right: parent.right
+ // anchors.bottom: parent.bottom
+ // anchors.left: drawer.right
+ //
+ // KeyNavigation.left: drawer
+ // }
+
+
+ FirelightDialog {
+ id: closeAppConfirmationDialog
+ text: "Are you sure you want to close Firelight?"
+
+ onAccepted: {
+ Qt.callLater(Qt.quit)
+ }
+ }
+}
\ No newline at end of file
diff --git a/qml/SettingsPage.qml b/qml/screens/SettingsScreen.qml
similarity index 87%
rename from qml/SettingsPage.qml
rename to qml/screens/SettingsScreen.qml
index 3ae0efc..76f1700 100644
--- a/qml/SettingsPage.qml
+++ b/qml/screens/SettingsScreen.qml
@@ -15,6 +15,8 @@ Item {
color: "#101114"
height: parent.height
width: leftSpacer.width + menu.width + contentPane.horizontalPadding + 6
+
+
}
RowLayout {
@@ -32,7 +34,7 @@ Item {
Pane {
id: contentPane
Layout.fillWidth: true
- Layout.horizontalStretchFactor: 6
+ Layout.horizontalStretchFactor: 8
Layout.fillHeight: true
verticalPadding: 80
@@ -40,6 +42,7 @@ Item {
background: Item {
}
+
ColumnLayout {
id: menu
spacing: 4
@@ -96,6 +99,19 @@ Item {
}
}
}
+ FirelightMenuItem {
+ labelText: "Platforms"
+ // labelIcon: "\ue88e"
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+ Layout.preferredHeight: 40
+ checked: root.section === "platforms"
+ onToggled: {
+ if (checked) {
+ root.section = "platforms"
+ }
+ }
+ }
FirelightMenuItem {
labelText: "System"
// labelIcon: "\uf522"
@@ -165,6 +181,14 @@ Item {
rightHalf.replace(retroAchievementSettings)
} else if (root.section === "audiovideo") {
rightHalf.replace(videoSettings)
+ } else if (root.section === "platforms/gbc") {
+ rightHalf.replace(gbcSettings)
+ } else if (root.section === "platforms/snes") {
+ rightHalf.replace(snesSettings)
+ } else if (root.section === "platforms") {
+ rightHalf.replace(platformSettings)
+ } else if (root.section === "platforms/gba") {
+ rightHalf.replace(gbaSettings)
}
}
}
@@ -258,6 +282,12 @@ Item {
}
}
+ Component {
+ id: platformSettings
+ PlatformSettingsPage {
+ }
+ }
+
Component {
id: librarySettings
LibrarySettings {
@@ -276,6 +306,24 @@ Item {
}
}
+ Component {
+ id: gbcSettings
+ GameBoyColorSettings {
+ }
+ }
+
+ Component {
+ id: gbaSettings
+ GbaSettings {
+ }
+ }
+
+ Component {
+ id: snesSettings
+ SnesSettings {
+ }
+ }
+
Component {
id: debugSettings
DebugPage {
diff --git a/qml/settings/RetroAchievementSettings.qml b/qml/settings/RetroAchievementSettings.qml
index 2735f88..09577ed 100644
--- a/qml/settings/RetroAchievementSettings.qml
+++ b/qml/settings/RetroAchievementSettings.qml
@@ -215,130 +215,131 @@ Flickable {
Layout.fillHeight: true
Layout.fillWidth: true
Layout.bottomMargin: 12
- text: "You can change this setting for individual games."
- font.pointSize: 10
+ text: "Only Hardcore mode is available at the moment."
+ font.pixelSize: 14
Layout.alignment: Qt.AlignLeft
font.family: Constants.regularFontFamily
+ // font.weight: Font.
wrapMode: Text.WordWrap
- color: "#c1c1c1"
+ color: ColorPalette.neutral300
}
- RadioButton {
- Layout.fillWidth: true
- Layout.preferredHeight: 140
- checked: achievement_manager.defaultToHardcore
- horizontalPadding: 12
- verticalPadding: 6
-
- background: Rectangle {
- radius: 8
- color: "#333333"
- border.color: parent.checked ? "#f16205" : "transparent"
- border.width: 3
- }
-
- indicator: Item {
- }
-
- onCheckedChanged: {
- if (checked) {
- achievement_manager.defaultToHardcore = true
- }
- }
-
- HoverHandler {
- id: hardcoreHover
- cursorShape: Qt.PointingHandCursor
- }
-
- contentItem: ColumnLayout {
- Text {
- Layout.fillWidth: true
- text: "Hardcore mode"
- color: "white"
- font.pointSize: 11
- Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
- font.family: Constants.regularFontFamily
- font.weight: Font.DemiBold
- }
-
- Text {
- text: "Challenge yourself and earn Mastery points by disabling save states, cheats, and rewinding."
- Layout.fillWidth: true
- wrapMode: Text.WordWrap
- font.pointSize: 11
- font.family: Constants.regularFontFamily
- font.weight: Font.Normal
- color: "#c1c1c1"
- }
- Item {
- Layout.fillHeight: true
- }
- }
- }
-
- RadioButton {
- Layout.fillWidth: true
- Layout.preferredHeight: 60
- horizontalPadding: 12
- checked: !achievement_manager.defaultToHardcore
- verticalPadding: 6
-
- background: Rectangle {
- radius: 8
- color: "#333333"
- border.color: parent.checked ? "#f16205" : "transparent"
- border.width: 3
- }
-
- indicator: Item {
- }
-
- onCheckedChanged: {
- if (checked) {
- achievement_manager.defaultToHardcore = false
- }
- }
-
- Connections {
- target: achievement_manager
-
- function onDefaultToHardcoreChanged() {
- console.log(":)")
- }
- }
-
- HoverHandler {
- id: casualHover
- cursorShape: Qt.PointingHandCursor
- }
-
- contentItem: ColumnLayout {
- Text {
- Layout.fillWidth: true
- text: "Casual mode"
- color: "white"
- font.pointSize: 11
- Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
- font.family: Constants.regularFontFamily
- font.weight: Font.DemiBold
- }
-
- Text {
- text: "Play with save states, cheats, and rewinding enabled."
- Layout.fillWidth: true
- wrapMode: Text.WordWrap
- font.pointSize: 11
- font.family: Constants.regularFontFamily
- font.weight: Font.Normal
- color: "#c1c1c1"
- }
-
- Item {
- Layout.fillHeight: true
- }
- }
- }
+ // RadioButton {
+ // Layout.fillWidth: true
+ // Layout.preferredHeight: 140
+ // checked: achievement_manager.defaultToHardcore
+ // horizontalPadding: 12
+ // verticalPadding: 6
+ //
+ // background: Rectangle {
+ // radius: 8
+ // color: "#333333"
+ // border.color: parent.checked ? "#f16205" : "transparent"
+ // border.width: 3
+ // }
+ //
+ // indicator: Item {
+ // }
+ //
+ // onCheckedChanged: {
+ // if (checked) {
+ // achievement_manager.defaultToHardcore = true
+ // }
+ // }
+ //
+ // HoverHandler {
+ // id: hardcoreHover
+ // cursorShape: Qt.PointingHandCursor
+ // }
+ //
+ // contentItem: ColumnLayout {
+ // Text {
+ // Layout.fillWidth: true
+ // text: "Hardcore mode"
+ // color: "white"
+ // font.pointSize: 11
+ // Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+ // font.family: Constants.regularFontFamily
+ // font.weight: Font.DemiBold
+ // }
+ //
+ // Text {
+ // text: "Challenge yourself and earn Mastery points by disabling save states, cheats, and rewinding."
+ // Layout.fillWidth: true
+ // wrapMode: Text.WordWrap
+ // font.pointSize: 11
+ // font.family: Constants.regularFontFamily
+ // font.weight: Font.Normal
+ // color: "#c1c1c1"
+ // }
+ // Item {
+ // Layout.fillHeight: true
+ // }
+ // }
+ // }
+ //
+ // RadioButton {
+ // Layout.fillWidth: true
+ // Layout.preferredHeight: 60
+ // horizontalPadding: 12
+ // checked: !achievement_manager.defaultToHardcore
+ // verticalPadding: 6
+ //
+ // background: Rectangle {
+ // radius: 8
+ // color: "#333333"
+ // border.color: parent.checked ? "#f16205" : "transparent"
+ // border.width: 3
+ // }
+ //
+ // indicator: Item {
+ // }
+ //
+ // onCheckedChanged: {
+ // if (checked) {
+ // achievement_manager.defaultToHardcore = false
+ // }
+ // }
+ //
+ // Connections {
+ // target: achievement_manager
+ //
+ // function onDefaultToHardcoreChanged() {
+ // console.log(":)")
+ // }
+ // }
+ //
+ // HoverHandler {
+ // id: casualHover
+ // cursorShape: Qt.PointingHandCursor
+ // }
+ //
+ // contentItem: ColumnLayout {
+ // Text {
+ // Layout.fillWidth: true
+ // text: "Casual mode"
+ // color: "white"
+ // font.pointSize: 11
+ // Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+ // font.family: Constants.regularFontFamily
+ // font.weight: Font.DemiBold
+ // }
+ //
+ // Text {
+ // text: "Play with save states, cheats, and rewinding enabled."
+ // Layout.fillWidth: true
+ // wrapMode: Text.WordWrap
+ // font.pointSize: 11
+ // font.family: Constants.regularFontFamily
+ // font.weight: Font.Normal
+ // color: "#c1c1c1"
+ // }
+ //
+ // Item {
+ // Layout.fillHeight: true
+ // }
+ // }
+ // }
// ToggleOption {
// Layout.fillWidth: true
diff --git a/qml/SettingsItem.qml b/qml/settings/SettingsItem.qml
similarity index 100%
rename from qml/SettingsItem.qml
rename to qml/settings/SettingsItem.qml
diff --git a/qml_tests/common/tst_DetailsButton.qml b/qml_tests/common/tst_DetailsButton.qml
new file mode 100644
index 0000000..a57fbef
--- /dev/null
+++ b/qml_tests/common/tst_DetailsButton.qml
@@ -0,0 +1,37 @@
+import QtQuick 2.3
+import QtTest 1.0
+import QMLFirelightTest
+
+Item {
+ width: 400
+ height: 400
+
+ DetailsButton {
+ id: button
+ width: 48
+ height: 48
+ }
+
+ TestCase {
+ name: "DetailsButton"
+ when: windowShown
+
+ function test_background_color() {
+ compare(button.background.color, "#00000000", "Background is transparent")
+ mouseMove(button)
+ verify(button.hovered, "Button is hovered")
+ compare(button.background.color, "#ffffff", "Background color is correct 2")
+ verify(button.background.opacity === 0.1, "Background opacity is correct")
+ }
+
+ function test_radius() {
+ compare(button.background.radius, 24, "Radius is correct")
+ }
+
+ function test_symbol() {
+ compare(button.contentItem.text, "\ue5d4", "Symbol is correct")
+ compare(button.contentItem.color, "#ffffff", "Symbol color is correct")
+ compare(button.contentItem.font.pointSize, 16, "Symbol size is correct")
+ }
+ }
+}
diff --git a/qml_tests/common/tst_NavigationTabBar.qml b/qml_tests/common/tst_NavigationTabBar.qml
new file mode 100644
index 0000000..4478318
--- /dev/null
+++ b/qml_tests/common/tst_NavigationTabBar.qml
@@ -0,0 +1,112 @@
+import QtQuick 2.3
+import QtTest 1.0
+import QMLFirelightTest
+
+Item {
+ width: 400
+ height: 400
+
+ NavigationTabBar {
+ id: control
+ width: parent.width
+ height: 100
+
+ tabs: ["Hi", "There"]
+ tabWidth: 100
+ }
+
+ TestCase {
+ name: "NavigationTabBar"
+ when: windowShown
+
+ function init() {
+ control.currentIndex = 0
+ }
+
+ function test_clicking() {
+ verify(control.contentChildren.length === 2)
+
+ const buttonOne = control.contentChildren[0]
+ const buttonTwo = control.contentChildren[1]
+
+ compare(control.currentIndex, 0, "currentIndex is correct")
+ mouseClick(buttonTwo)
+ compare(control.currentIndex, 1, "currentIndex is correct")
+ mouseClick(buttonOne)
+ compare(control.currentIndex, 0, "currentIndex is correct")
+
+ // verify(control.contentChildren[0].text === "Hi")
+ }
+
+ function test_highlight() {
+ verify(control.contentChildren.length === 2)
+ const highlight = findChild(control, "background")
+
+ const buttonOne = control.contentChildren[0]
+ const buttonTwo = control.contentChildren[1]
+
+ compare(control.currentIndex, 0, "currentIndex is correct")
+ compare(highlight.width, 100, "highlight width is correct")
+ compare(highlight.height, 2, "highlight height is correct")
+ compare(highlight.radius, 1, "highlight radius is correct")
+ compare(highlight.color, "#ffffff", "highlight color is correct")
+ compare(highlight.y, 100, "highlight y is correct")
+ compare(highlight.x, 0, "highlight x is correct")
+
+ mouseClick(buttonTwo)
+ wait(200)
+ compare(control.currentIndex, 1, "currentIndex is correct")
+ compare(highlight.x, 100, "highlight x is correct")
+ }
+
+ function test_tabWidth() {
+ verify(control.contentChildren.length === 2)
+
+ const buttonOne = control.contentChildren[0]
+ const buttonTwo = control.contentChildren[1]
+
+ compare(buttonOne.width, 100, "buttonOne width is correct")
+ compare(buttonTwo.width, 100, "buttonTwo width is correct")
+ }
+
+ function test_tab_labels() {
+ verify(control.contentChildren.length === 2)
+
+ const buttonOne = control.contentChildren[0]
+ const buttonTwo = control.contentChildren[1]
+
+ compare(control.currentIndex, 0, "currentIndex is correct")
+ const textOne = findChild(buttonOne, "text")
+ const textTwo = findChild(buttonTwo, "text")
+
+ compare(textOne.text, "Hi", "buttonOne text is correct")
+ compare(textOne.color, "#ffffff", "text color is correct")
+ compare(textOne.font.pointSize, 11, "text pointSize is correct")
+ compare(textOne.font.weight, Font.Bold, "text weight is correct")
+ verify(buttonOne.checked, "buttonOne is checked")
+
+ compare(textTwo.text, "There", "buttonTwo text is correct")
+ compare(textTwo.color, "#ffffff", "text color is correct")
+ compare(textTwo.font.pointSize, 11, "text pointSize is correct")
+ compare(textTwo.font.weight, Font.Normal, "text weight is correct")
+ verify(!buttonTwo.checked, "buttonTwo is not checked")
+
+ mouseClick(buttonTwo)
+
+ compare(textTwo.text, "There", "buttonTwo text is correct")
+ compare(textTwo.color, "#ffffff", "text color is correct")
+ compare(textTwo.font.pointSize, 11, "text pointSize is correct")
+ compare(textTwo.font.weight, Font.Bold, "text weight is correct")
+ verify(buttonTwo.checked, "buttonTwo is checked")
+
+ compare(textOne.text, "Hi", "buttonOne text is correct")
+ compare(textOne.color, "#ffffff", "text color is correct")
+ compare(textOne.font.pointSize, 11, "text pointSize is correct")
+ compare(textOne.font.weight, Font.Normal, "text weight is correct")
+ verify(!buttonOne.checked, "buttonOne is not checked")
+
+
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/qml_tests/common/tst_RightClickMenu.qml b/qml_tests/common/tst_RightClickMenu.qml
index 625667e..9ac1710 100644
--- a/qml_tests/common/tst_RightClickMenu.qml
+++ b/qml_tests/common/tst_RightClickMenu.qml
@@ -1,10 +1,29 @@
import QtQuick 2.3
-import QtTest 1.0
-TestCase {
- name: "MathTests"
+Item {
+ width: 400
+ height: 400
- function test_math() {
- compare(2 + 2, 4, "2 + 2 = 4")
- }
-}
\ No newline at end of file
+ // DetailsButton {
+ // id: button
+ // width: 48
+ // height: 48
+ // }
+ //
+ // TestCase {
+ // name: "DetailsButton"
+ // when: windowShown
+ //
+ // function test_background_color() {
+ // compare(button.background.color, "#00000000", "Background is transparent")
+ // mouseMove(button)
+ // verify(button.hovered, "Button is hovered")
+ // compare(button.background.color, "#ffffff", "Background color is correct 2")
+ // verify(button.background.opacity === 0.1, "Background opacity is correct")
+ // }
+ //
+ // function test_radius() {
+ // compare(button.background.radius, 24, "Radius is correct")
+ // }
+ // }
+}
diff --git a/qml_tests/library/tst_GameGridItemDelegate.qml b/qml_tests/library/tst_GameGridItemDelegate.qml
new file mode 100644
index 0000000..5066e95
--- /dev/null
+++ b/qml_tests/library/tst_GameGridItemDelegate.qml
@@ -0,0 +1,151 @@
+import QtQuick 2.3
+import QtTest 1.0
+import QMLFirelightTest
+
+Item {
+ id: root
+ width: 400
+ height: 400
+
+ SignalSpy {
+ id: spy
+ target: button
+ signalName: "openDetails"
+ }
+
+ SignalSpy {
+ id: startGameSpy
+ target: button
+ signalName: "startGame"
+ }
+
+ SignalSpy {
+ id: manageSaveDataSpy
+ target: button
+ signalName: "manageSaveData"
+ }
+
+ GameGridItemDelegate {
+ id: button
+ cellWidth: 100
+ cellHeight: 100
+ cellSpacing: 8
+ anchors.centerIn: parent
+
+ index: 0
+ model: {
+ return {
+ "id": 5,
+ "display_name": "Test",
+ "platform_name": "Test description"
+ }
+ }
+ }
+
+ TestCase {
+ name: "GameGridItemDelegate"
+ when: windowShown
+
+ function init() {
+ spy.clear()
+ startGameSpy.clear()
+ }
+
+ function test_size_is_correct() {
+ compare(button.width, 100, "Width is correct")
+ compare(button.height, 100, "Height is correct")
+ }
+
+ function test_background_color_changes_on_hover() {
+ const background = findChild(button, "background")
+
+ compare(background.color, "#25282c", "Background is correct when not hovering")
+ mouseMove(button)
+ compare(background.color, "#3a3e45", "Background is correct when hovering")
+ }
+
+ function test_left_click_emits_detailsOpened() {
+ mouseClick(button)
+ compare(spy.count, 1, "openDetails signal was emitted")
+ compare(spy.signalArguments[0][0], 5, "openDetails signal was emitted with correct id")
+ }
+
+ function test_right_click_opens_menu() {
+ const rightClick = findChild(button, "rightClickMenu")
+
+ compare(rightClick.visible, false, "Right click is not visible")
+
+ mouseClick(button, button.width / 2, button.height / 2, Qt.RightButton)
+ compare(rightClick.visible, true, "Right click is visible")
+
+ mouseClick(button, 10, 10, Qt.RightButton)
+ compare(rightClick.visible, true, "Right click is still visible")
+ }
+
+ function test_right_click_menu_play_game() {
+ const rightClick = findChild(button, "rightClickMenu")
+ mouseClick(button, button.width / 2, button.height / 2, Qt.RightButton)
+
+ const play = rightClick.itemAt(0)
+ compare(play.enabled, true, "Play button is enabled")
+ mouseClick(play)
+
+ compare(startGameSpy.count, 1, "startGame signal was emitted")
+ compare(startGameSpy.signalArguments[0][0], 5, "startGame signal was emitted with correct id")
+ }
+
+ function test_right_click_menu_view_details() {
+ const rightClick = findChild(button, "rightClickMenu")
+ mouseClick(button, button.width / 2, button.height / 2, Qt.RightButton)
+
+ const viewDetails = rightClick.itemAt(1)
+ compare(viewDetails.enabled, true, "View details is enabled")
+ mouseClick(viewDetails)
+
+ compare(spy.count, 1, "openDetails signal was emitted")
+ compare(spy.signalArguments[0][0], 5, "openDetails signal was emitted with correct id")
+ }
+
+ function test_right_click_menu_manage_save_data() {
+ const rightClick = findChild(button, "rightClickMenu")
+ mouseClick(button, button.width / 2, button.height / 2, Qt.RightButton)
+
+ const manageSaveDataSpy = rightClick.itemAt(3)
+ compare(manageSaveDataSpy.enabled, true, "View details is enabled")
+ mouseClick(manageSaveDataSpy)
+
+ compare(manageSaveDataSpy.count, 1, "manageSaveDataSpy signal was emitted")
+ compare(manageSaveDataSpy.signalArguments[0][0], 5, "manageSaveDataSpy signal was emitted with correct id")
+ }
+
+ function test_esc_closes_right_click_menu() {
+ const rightClick = findChild(button, "rightClickMenu")
+
+ mouseClick(button, button.width / 2, button.height / 2, Qt.RightButton)
+ compare(rightClick.visible, true, "Right click is visible")
+
+ keyClick(Qt.Key_Escape)
+ compare(rightClick.visible, false, "Right click is not visible")
+ }
+
+ function test_click_outside_closes_right_click_menu() {
+ const rightClick = findChild(button, "rightClickMenu")
+
+ mouseClick(button, button.width / 2, button.height / 2, Qt.RightButton)
+ compare(rightClick.visible, true, "Right click is visible")
+
+ mouseClick(button, button.width, button.height, Qt.LeftButton)
+ compare(rightClick.visible, false, "Right click is not visible")
+ }
+
+ function test_right_click_menu_has_correct_items() {
+ const rightClick = findChild(button, "rightClickMenu")
+
+ compare(rightClick.count, 4, "Right click menu has 3 items")
+ compare(rightClick.itemAt(0).text, "Play Test", "First item is 'Play'")
+ compare(rightClick.itemAt(1).text, "View details", "Second item is 'View details'")
+ compare(rightClick.itemAt(2).height - (rightClick.itemAt(2).verticalPadding * 2), 1, "Third item is menu separator") // It's stupid but it works.
+ compare(rightClick.itemAt(3).text, "Manage save data", "Second item is 'Manage save data'")
+ }
+ }
+}
diff --git a/qml_tests/library/tst_LibraryPage2.qml b/qml_tests/library/tst_LibraryPage2.qml
new file mode 100644
index 0000000..fd66be9
--- /dev/null
+++ b/qml_tests/library/tst_LibraryPage2.qml
@@ -0,0 +1,61 @@
+import QtQuick 2.3
+import QtTest 1.0
+import QMLFirelightTest
+
+Item {
+ id: root
+ width: 800
+ height: 800
+
+ LibraryPage2 {
+ id: page
+ anchors.fill: parent
+ }
+
+ TestCase {
+ name: "LibraryPage"
+ when: windowShown
+
+ function test_width_data() {
+ return [
+ {rootWidth: 200, expectedNumCells: 1},
+ {rootWidth: 300, expectedNumCells: 1},
+ {rootWidth: 400, expectedNumCells: 2},
+ {rootWidth: 500, expectedNumCells: 2},
+ {rootWidth: 600, expectedNumCells: 3},
+ {rootWidth: 700, expectedNumCells: 3},
+ {rootWidth: 800, expectedNumCells: 4},
+ {rootWidth: 900, expectedNumCells: 4},
+ {rootWidth: 1000, expectedNumCells: 5},
+ {rootWidth: 1100, expectedNumCells: 5},
+ {rootWidth: 1200, expectedNumCells: 6},
+ {rootWidth: 1300, expectedNumCells: 6},
+ {rootWidth: 1400, expectedNumCells: 7},
+ {rootWidth: 1500, expectedNumCells: 7},
+ {rootWidth: 1600, expectedNumCells: 7}, // Make sure it caps at 7
+ {rootWidth: 1700, expectedNumCells: 7},
+ {rootWidth: 1800, expectedNumCells: 7},
+ {rootWidth: 1900, expectedNumCells: 7}
+ ]
+ }
+
+ function test_width(data) {
+ root.width = data.rootWidth
+ const grid = findChild(page, "GridView")
+ compare(grid.width, grid.cellWidth * data.expectedNumCells, "Width is correct")
+ }
+
+ function test_default_values() {
+ const grid = findChild(page, "GridView")
+ compare(grid.preferredHighlightBegin, 0.33 * grid.height, "Preferred highlight begin is correct")
+ compare(grid.preferredHighlightEnd, 0.66 * grid.height, "Preferred highlight end is correct")
+ compare(grid.highlightRangeMode, GridView.ApplyRange, "Highlight range mode is correct")
+ verify(grid.clip, "Clip is enabled")
+ verify(grid.focus, "Focus is enabled")
+ compare(grid.boundsBehavior, GridView.StopAtBounds, "Bounds behavior is correct")
+ compare(grid.cellSpacing, 12, "Cell spacing is correct")
+ compare(grid.cellWidth, 192, "Cell width is correct")
+ compare(grid.cellHeight, 272, "Cell height is correct")
+ }
+ }
+}
diff --git a/resources.qrc b/resources.qrc
index 3e4bd1f..9c22451 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -5,10 +5,22 @@
- assets/Lexend-VariableFont_wght.ttf
+ assets/LexendDeca[wght].ttf
assets/NunitoSans_10pt-Regular.ttf
assets/Lexend-Medium.ttf
assets/NunitoSans_10pt_SemiCondensed-Black.ttf
assets/MaterialSymbols.ttf
+
+ _img/nes.svg
+ _img/SNES.svg
+ _img/gb.svg
+ _img/gbc.svg
+ _img/gba.svg
+ _img/N64.svg
+ _img/SwitchPro.svg
+
+
+ _img/SwitchPro.svg
+
\ No newline at end of file
diff --git a/src/app/audio_manager.cpp b/src/app/audio_manager.cpp
index 6e24e09..ab42e20 100644
--- a/src/app/audio_manager.cpp
+++ b/src/app/audio_manager.cpp
@@ -10,10 +10,10 @@ void AudioManager::initialize(const double new_freq) {
SDL_AudioSpec want, have;
SDL_memset(&want, 0, sizeof(want));
- want.freq = new_freq; // Sample rate (e.g., 44.1 kHz)
+ want.freq = new_freq; // Sample rate (e.g., 44.1 kHz)
want.format = AUDIO_S16; // Audio format (16-bit signed)
- want.channels = 2; // Number of audio channels (stereo)
- want.samples = 4096; // Audio buffer size (samples)
+ want.channels = 2; // Number of audio channels (stereo)
+ want.samples = 128; // Audio buffer size (samples)
want.callback = nullptr;
this->audioDevice = SDL_OpenAudioDevice(nullptr, 0, &want, &have, 0);
@@ -30,3 +30,8 @@ void AudioManager::toggle_mute() {
muted = !muted;
SDL_PauseAudioDevice(audioDevice, muted);
}
+
+AudioManager::~AudioManager() {
+ printf("Destroying AudioManager\n");
+ SDL_CloseAudioDevice(this->audioDevice);
+}
diff --git a/src/app/audio_manager.hpp b/src/app/audio_manager.hpp
index 68d4d62..20dac7f 100644
--- a/src/app/audio_manager.hpp
+++ b/src/app/audio_manager.hpp
@@ -6,10 +6,15 @@
class AudioManager : public IAudioDataReceiver {
public:
size_t receive(const int16_t *data, size_t numFrames) override;
+
void initialize(double new_freq) override;
+
bool is_muted() override;
+
void toggle_mute() override;
+ ~AudioManager() override;
+
private:
SDL_AudioDeviceID audioDevice = 0;
bool muted = false;
diff --git a/src/app/db/sqlite_content_database.cpp b/src/app/db/sqlite_content_database.cpp
index e8f3ee5..593919e 100644
--- a/src/app/db/sqlite_content_database.cpp
+++ b/src/app/db/sqlite_content_database.cpp
@@ -320,6 +320,10 @@ namespace firelight::db {
return {Platform{.id = 13}};
}
+ if (ext == ".nes") {
+ return {Platform{.id = 5}};
+ }
+
return {};
}
diff --git a/src/app/db/sqlite_userdata_database.cpp b/src/app/db/sqlite_userdata_database.cpp
index 532f2ee..381724e 100644
--- a/src/app/db/sqlite_userdata_database.cpp
+++ b/src/app/db/sqlite_userdata_database.cpp
@@ -5,6 +5,8 @@
#include
namespace firelight::db {
+ constexpr auto DATABASE_PREFIX = "userdata_";
+
SqliteUserdataDatabase::SqliteUserdataDatabase(
const std::filesystem::path &dbFile)
: m_database_path(dbFile) {
@@ -64,6 +66,18 @@ namespace firelight::db {
spdlog::error("Table creation failed: {}",
createInputMappings.lastError().text().toStdString());
}
+
+ QSqlQuery createPlatformSettings(m_database);
+ createPlatformSettings.prepare("CREATE TABLE IF NOT EXISTS platform_settings("
+ "platform_id INTEGER NOT NULL, "
+ "key TEXT NOT NULL,"
+ "value TEXT NOT NULL,"
+ "UNIQUE(platform_id, key));");
+
+ if (!createPlatformSettings.exec()) {
+ spdlog::error("Table creation failed: {}",
+ createPlatformSettings.lastError().text().toStdString());
+ }
}
SqliteUserdataDatabase::~SqliteUserdataDatabase() {
@@ -277,4 +291,67 @@ namespace firelight::db {
//
// return std::nullopt;
}
+
+ std::optional SqliteUserdataDatabase::getPlatformSettingValue(
+ const int platformId, const std::string key) {
+ const QString queryString = "SELECT value FROM platform_settings "
+ "WHERE platform_id = :platformId AND key = :key LIMIT 1;";
+ auto query = QSqlQuery(m_database);
+ query.prepare(queryString);
+
+ query.bindValue(":platformId", platformId);
+ query.bindValue(":key", QString::fromStdString(key));
+
+ if (!query.exec()) {
+ spdlog::warn("Get from platform_settings failed: {}",
+ query.lastError().text().toStdString());
+ return std::nullopt;
+ }
+
+ if (query.next()) {
+ return query.value("value").toString().toStdString();
+ }
+
+ return std::nullopt;
+ }
+
+ std::map SqliteUserdataDatabase::getAllPlatformSettings(int platformId) {
+ const QString queryString = "SELECT key, value FROM platform_settings "
+ "WHERE platform_id = :platformId;";
+ auto query = QSqlQuery(m_database);
+ query.prepare(queryString);
+
+ query.bindValue(":platformId", platformId);
+
+ if (!query.exec()) {
+ spdlog::warn("Get all from platform_settings failed: {}",
+ query.lastError().text().toStdString());
+ return {};
+ }
+
+ std::map settings;
+ while (query.next()) {
+ settings[query.value("key").toString().toStdString()] = query.value("value").toString().toStdString();
+ }
+
+ return settings;
+ }
+
+ void SqliteUserdataDatabase::setPlatformSettingValue(const int platformId, const std::string key,
+ const std::string value) {
+ const QString queryString = "INSERT OR REPLACE INTO platform_settings "
+ "(platform_id, key, value) "
+ "VALUES (:platformId, :key, :value);";
+ auto query = QSqlQuery(m_database);
+ query.prepare(queryString);
+
+ query.bindValue(":platformId", platformId);
+ query.bindValue(":key", QString::fromStdString(key));
+ query.bindValue(":value", QString::fromStdString(value));
+
+ if (!query.exec()) {
+ spdlog::warn("Insert into platform_settings failed: {}",
+ query.lastError().text().toStdString());
+ }
+ }
} // namespace firelight::db
diff --git a/src/app/db/sqlite_userdata_database.hpp b/src/app/db/sqlite_userdata_database.hpp
index d4b6c93..642d465 100644
--- a/src/app/db/sqlite_userdata_database.hpp
+++ b/src/app/db/sqlite_userdata_database.hpp
@@ -3,7 +3,6 @@
#include "firelight/userdata_database.hpp"
#include
#include
-#include
namespace firelight::db {
class SqliteUserdataDatabase final : public IUserdataDatabase {
@@ -35,6 +34,13 @@ namespace firelight::db {
std::optional
getLatestPlaySession(std::string contentId) override;
+ std::optional
+ getPlatformSettingValue(int platformId, std::string key) override;
+
+ std::map getAllPlatformSettings(int platformId) override;
+
+ void setPlatformSettingValue(int platformId, std::string key, std::string value) override;
+
private:
std::filesystem::path m_database_path;
QSqlDatabase m_database;
diff --git a/src/app/emulation_manager.cpp b/src/app/emulation_manager.cpp
index 769d4dc..510d3c5 100644
--- a/src/app/emulation_manager.cpp
+++ b/src/app/emulation_manager.cpp
@@ -19,124 +19,74 @@
#include
#include
#include
-#include
+
+#include "platform_metadata.hpp"
constexpr int SAVE_FREQUENCY_MILLIS = 10000;
EmulationManager::EmulationManager(QQuickItem *parent)
- : QQuickFramebufferObject(parent) {
+ : QQuickFramebufferObject(parent) {
+ printf("Creating EmulationManager\n");
setTextureFollowsItemSize(false);
setMirrorVertically(true);
setFlag(ItemHasContents);
- connect(this, &EmulationManager::emulationStarted, this,
- &QQuickFramebufferObject::update, Qt::QueuedConnection);
- connect(this, &EmulationManager::gamePaused, this,
- &QQuickFramebufferObject::update, Qt::QueuedConnection);
- connect(this, &EmulationManager::gameResumed, this,
- &QQuickFramebufferObject::update, Qt::QueuedConnection);
-
- connect(
- this, &EmulationManager::gameLoadSucceeded, this,
- [this] {
- m_gameLoadedSignalReady = true;
- if (m_achievementsLoadedSignalReady) {
- emit readyToStart();
- m_gameLoadedSignalReady = false;
- m_achievementsLoadedSignalReady = false;
- }
- },
- Qt::QueuedConnection);
-
- connect(
- getAchievementManager(),
- &firelight::achievements::RAClient::gameLoadSucceeded, this,
- [this] {
- m_achievementsLoadedSignalReady = true;
- if (m_gameLoadedSignalReady) {
- emit readyToStart();
- m_gameLoadedSignalReady = false;
- m_achievementsLoadedSignalReady = false;
- }
- },
- Qt::QueuedConnection);
-
- m_autosaveTimer.setInterval(SAVE_FREQUENCY_MILLIS);
- m_autosaveTimer.setSingleShot(false);
- m_autosaveTimer.callOnTimeout([this] {
- spdlog::info("Autosaving SRAM data (interval {}ms)", SAVE_FREQUENCY_MILLIS);
- save();
- });
+ // connect(this, &EmulationManager::emulationStarted, this,
+ // &QQuickFramebufferObject::update, Qt::QueuedConnection);
+ // connect(this, &EmulationManager::gamePaused, this,
+ // &QQuickFramebufferObject::update, Qt::QueuedConnection);
+ // connect(this, &EmulationManager::gameResumed, this,
+ // &QQuickFramebufferObject::update, Qt::QueuedConnection);
+
+ // connect(
+ // this, &EmulationManager::gameLoadSucceeded, this,
+ // [this] {
+ // m_currentPlaySession = std::make_unique();
+ // m_currentPlaySession->contentId = m_currentEntry.contentId;
+ // m_currentPlaySession->startTime = QDateTime::currentMSecsSinceEpoch();
+ // m_currentPlaySession->slotNumber = m_currentEntry.activeSaveSlot;
+ //
+ // m_playtimeTimer.start();
+ // QMetaObject::invokeMethod(&m_autosaveTimer, "start", Qt::QueuedConnection);
+ //
+ // emit emulationStarted();
+ // },
+ // Qt::QueuedConnection);
+
+ // m_autosaveTimer.setInterval(SAVE_FREQUENCY_MILLIS);
+ // m_autosaveTimer.setSingleShot(false);
+ // m_autosaveTimer.callOnTimeout([this] {
+ // spdlog::info("Autosaving SRAM data (interval {}ms)", SAVE_FREQUENCY_MILLIS);
+ // save();
+ // });
}
EmulationManager::~EmulationManager() {
- if (!m_isRunning) {
- return;
- }
-
- QMetaObject::invokeMethod(&m_autosaveTimer, "stop", Qt::QueuedConnection);
-
- m_isRunning = false;
-
- if (m_currentPlaySession) {
- m_currentPlaySession->endTime = QDateTime::currentMSecsSinceEpoch();
+ printf("Destroying EmulationManager\n");
- const auto timerValue = m_playtimeTimer.restart();
- if (!m_paused) {
- m_currentPlaySession->unpausedDurationMillis += timerValue;
- }
-
- const auto session = m_currentPlaySession.release();
- getUserdataManager()->createPlaySession(*session);
- }
-
- save(true);
+ // QMetaObject::invokeMethod(&m_autosaveTimer, "stop", Qt::QueuedConnection);
+ //
+ // if (m_currentPlaySession) {
+ // m_currentPlaySession->endTime = QDateTime::currentMSecsSinceEpoch();
+ //
+ // const auto timerValue = m_playtimeTimer.restart();
+ // if (!m_paused) {
+ // m_currentPlaySession->unpausedDurationMillis += timerValue;
+ // }
+ //
+ // const auto session = m_currentPlaySession.release();
+ // getUserdataManager()->createPlaySession(*session);
+ // }
+ //
+ // save(true);
- if (m_core) {
- // m_core->unloadGame();
- // m_core->deinit();
- // m_core.reset();
- }
+ // getAchievementManager()->unloadGame();
}
QQuickFramebufferObject::Renderer *EmulationManager::createRenderer() const {
- return new EmulatorRenderer(this);
-}
-
-void EmulationManager::setGetProcAddressFunction(
- const std::function &getProcAddressFunction) {
- m_getProcAddressFunction = getProcAddressFunction;
-}
-
-std::function EmulationManager::consumeContextResetFunction() {
- if (m_resetContextFunction) {
- auto func = m_resetContextFunction;
- m_resetContextFunction = nullptr;
- return func;
- }
-
- return nullptr;
-}
-
-std::function EmulationManager::consumeContextDestroyFunction() {
- if (m_destroyContextFunction) {
- auto func = m_destroyContextFunction;
- m_destroyContextFunction = nullptr;
- return func;
- }
-
- return nullptr;
-}
-
-void EmulationManager::setReceiveVideoDataFunction(
- const std::function &receiveVideoDataFunction) {
- m_receiveVideoDataFunction = receiveVideoDataFunction;
+ return new EmulatorRenderer();
}
-void EmulationManager::setCurrentFboId(const int fboId) {
- m_currentFboId = fboId;
-}
QString EmulationManager::currentGameName() const {
return QString::fromStdString(m_currentEntry.displayName);
@@ -149,283 +99,50 @@ float EmulationManager::nativeAspectRatio() const {
return m_nativeAspectRatio;
}
-void EmulationManager::receive(const void *data, unsigned width,
- unsigned height, size_t pitch) {
- if (!m_usingHwRendering && m_receiveVideoDataFunction && data != nullptr) {
- m_receiveVideoDataFunction(data, width, height, pitch);
- }
-}
-
-proc_address_t EmulationManager::getProcAddress(const char *sym) {
- if (!m_getProcAddressFunction) {
- return nullptr;
- }
-
- return m_getProcAddressFunction(sym);
-}
-
-void EmulationManager::setResetContextFunc(context_reset_func resetFunction) {
- printf("Setting reset context function\n");
- m_usingHwRendering = true;
- m_resetContextFunction = resetFunction;
-}
-
-void EmulationManager::setDestroyContextFunc(
- context_destroy_func destroyFunction) {
- printf("Setting destroy context function\n");
- m_usingHwRendering = true;
- m_destroyContextFunction = destroyFunction;
-}
-
void EmulationManager::pauseGame() {
- if (!m_isRunning) {
- return;
- }
-
- if (!m_paused) {
- m_currentPlaySession->unpausedDurationMillis += m_playtimeTimer.restart();
- emit gamePaused();
- }
-
m_paused = true;
+ update();
}
void EmulationManager::resumeGame() {
- if (!m_isRunning) {
- return;
- }
-
- if (m_paused) {
- m_playtimeTimer.restart();
- emit gameResumed();
- }
-
m_paused = false;
+ update();
}
-void EmulationManager::startEmulation() {
- if (m_isRunning) {
- return;
- }
-
- QThreadPool::globalInstance()->start([this] {
- m_currentPlaySession = std::make_unique();
- m_currentPlaySession->contentId = m_currentEntry.contentId;
- m_currentPlaySession->startTime = QDateTime::currentMSecsSinceEpoch();
- m_currentPlaySession->slotNumber = m_currentEntry.activeSaveSlot;
-
- m_isRunning = true;
- m_paused = false;
- m_playtimeTimer.start();
- QMetaObject::invokeMethod(&m_autosaveTimer, "start", Qt::QueuedConnection);
-
- emit emulationStarted();
- });
+void EmulationManager::resetEmulation() {
+ m_shouldReset = true;
+ update();
}
-void EmulationManager::stopEmulation() {
- if (!m_isRunning) {
- return;
+void EmulationManager::setGeometry(int nativeWidth, int nativeHeight, float nativeAspectRatio) {
+ if (m_nativeWidth != nativeWidth) {
+ m_nativeWidth = nativeWidth;
+ emit nativeWidthChanged();
}
- QMetaObject::invokeMethod(&m_autosaveTimer, "stop", Qt::QueuedConnection);
-
- QThreadPool::globalInstance()->start([this] {
- m_currentPlaySession->endTime = QDateTime::currentMSecsSinceEpoch();
-
- const auto timerValue = m_playtimeTimer.restart();
- if (!m_paused) {
- m_currentPlaySession->unpausedDurationMillis += timerValue;
- }
-
- const auto session = m_currentPlaySession.get();
- getUserdataManager()->createPlaySession(*session);
- m_currentPlaySession.reset();
-
- getAchievementManager()->unloadGame();
- m_achievementsLoadedSignalReady = false;
- m_gameLoadedSignalReady = false;
-
- m_isRunning = false;
- save(true);
- m_nativeWidth = 0;
- m_nativeHeight = 0;
- m_nativeAspectRatio = 0;
-
- emit nativeWidthChanged();
+ if (m_nativeHeight != nativeHeight) {
+ m_nativeHeight = nativeHeight;
emit nativeHeightChanged();
-
- // if (m_destroyContextFunction) {
- // m_destroyContextFunction();
- // }
- shouldUnload = true;
-
- m_usingHwRendering = false;
- // m_core->unloadGame();
- // m_core->deinit();
- // m_core.reset();
-
- emit emulationStopped();
- });
-
- update();
-}
-
-void EmulationManager::resetEmulation() {
- if (m_core) {
- m_core->reset();
- update();
}
-}
-
-bool EmulationManager::isRunning() const { return m_isRunning; }
-
-void EmulationManager::save(const bool waitForFinish) {
- firelight::saves::Savefile saveData(
- m_core->getMemoryData(libretro::SAVE_RAM));
- // saveData.setImage(m_fbo->toImage());
-
- QFuture result =
- getSaveManager()->writeSaveDataForEntry(m_currentEntry, saveData);
- if (waitForFinish) {
- result.waitForFinished();
+ if (m_nativeAspectRatio != nativeAspectRatio) {
+ m_nativeAspectRatio = nativeAspectRatio;
+ emit nativeAspectRatioChanged();
}
}
-uintptr_t EmulationManager::getCurrentFramebufferId() { return m_currentFboId; }
-
-void EmulationManager::setSystemAVInfo(retro_system_av_info *info) {
- const auto width = info->geometry.base_width;
- const auto height = info->geometry.base_height;
-
- if (width > 0 && height > 0) {
- if (width != m_nativeWidth) {
- m_nativeWidth = width;
- emit nativeWidthChanged();
- }
-
- if (height != m_nativeHeight) {
- m_nativeHeight = height;
- emit nativeHeightChanged();
- }
-
- const auto aspectRatio =
- static_cast(width) / static_cast(height);
- if (aspectRatio != m_nativeAspectRatio) {
- m_nativeAspectRatio = aspectRatio;
- emit nativeAspectRatioChanged();
+void EmulationManager::setIsRunning(bool running) {
+ if (m_running != running) {
+ m_running = running;
+ if (m_running) {
+ emit emulationStarted();
+ } else {
+ emit emulationStopped();
}
}
}
-bool EmulationManager::runFrame() {
- if (shouldUnload) {
- m_core->unloadGame();
- m_core->deinit();
- m_core.reset();
-
- shouldUnload = false;
- }
-
- if (m_isRunning && !m_paused) {
- m_core->run(0);
- getAchievementManager()->doFrame(m_core.get(), m_currentEntry);
- return true;
- }
-
- return false;
-
- //
- // if (m_running) {
- // if (!m_paused) {
- // const auto frameBegin = SDL_GetPerformanceCounter();
- // lastTick = thisTick;
- // thisTick = SDL_GetPerformanceCounter();
- //
- // auto deltaTime =
- // (thisTick - lastTick) * 1000 /
- // (double)SDL_GetPerformanceFrequency();
- //
- // m_millisSinceLastSave += static_cast(deltaTime);
- // if (m_millisSinceLastSave < 0) {
- // m_millisSinceLastSave = 0;
- // }
- //
- // if (m_millisSinceLastSave >= SAVE_FREQUENCY_MILLIS) {
- // m_millisSinceLastSave = 0;
- // // gameImage = gameFbo->toImage();
- //
- // const auto state = core->serializeState();
- // const SuspendPoint suspendPoint{
- // .state = state, .timestamp =
- // QDateTime::currentMSecsSinceEpoch()};
- //
- // m_suspendPoints.push_back(suspendPoint);
- // }
- //
- // frameCount++;
- // if (frameSkipRatio == 0 || (frameCount % frameSkipRatio == 0)) {
- // core->run(deltaTime);
- //
- // auto frameEnd = SDL_GetPerformanceCounter();
- // auto frameDiff = ((frameEnd - frameBegin) * 1000 /
- // static_cast(SDL_GetPerformanceFrequency()));
- // totalFrameWorkDurationMillis += frameDiff;
- // numFrames++;
- //
- // if (numFrames == 300) {
- // printf("Average frame work duration: %fms\n",
- // totalFrameWorkDurationMillis / numFrames);
- // totalFrameWorkDurationMillis = 0;
- // numFrames = 0;
- // }
- // }
- // update();
- // }
- // m_ranLastFrame = true;
- //
- // // printf("Serialize size: %lu\n", core->getSerializeSize());
- //
- // if (m_fbo != nullptr) {
- // const auto image = m_fbo->toImage();
- // // printf("image size bytes: %lld\n", image.sizeInBytes());
- //
- // // Get a pointer to the raw data
- // // auto future = QtConcurrent::run([image] {
- // // const uchar *data = image.constBits();
- // // // Compress the image data using zlib
- // // uLongf compressedDataSize = compressBound(image.sizeInBytes());
- // // auto *compressedData = new uchar[compressedDataSize];
- // // if (compress2(compressedData, &compressedDataSize, data,
- // // image.sizeInBytes(), Z_BEST_COMPRESSION) != Z_OK) {
- // // // printf("Failed to compress image data\n");
- // // } else {
- // // printf("Compressed image size bytes: %lu\n",
- // compressedDataSize);
- // // // Now you can use 'compressedData' to transmit the compressed
- // // // image
- // // // // over a network connection Be sure to also transmit the
- // // size
- // // // of the
- // // // compressed data, which is compressedDataSize
- // // }
- // //
- // // delete[] compressedData;
- // // });
- //
- // // Now you can use 'data' to transmit the image over a network
- // // connection
- // // Be sure to also transmit the size of the data, which is
- // // image.sizeInBytes()
- // }
- // }
-}
-
void EmulationManager::loadLibraryEntry(int entryId) {
- m_gameLoadedSignalReady = false;
- m_achievementsLoadedSignalReady = false;
-
QThreadPool::globalInstance()->start([this, entryId] {
spdlog::info("Loading entry with id {}", entryId);
auto entry = getLibraryDatabase()->getLibraryEntry(entryId);
@@ -504,44 +221,12 @@ void EmulationManager::loadLibraryEntry(int entryId) {
saveData->getSaveRamData().size());
}
- std::string corePath;
- if (parent->platformId == 7) {
- corePath = "./system/_cores/mupen64plus_next_libretro.dll";
- } else if (parent->platformId == 6) {
- corePath = "./system/_cores/snes9x_libretro.dll";
- } else if (parent->platformId == 2) {
- corePath = "./system/_cores/gambatte_libretro.dll";
- } else if (parent->platformId == 1) {
- corePath = "./system/_cores/gambatte_libretro.dll";
- } else if (parent->platformId == 3) {
- corePath = "./system/_cores/mgba_libretro.dll";
- } else if (parent->platformId == 10) {
- corePath = "./system/_cores/melondsds_libretro.dll";
- } else if (parent->platformId == 13) {
- corePath = "./system/_cores/genesis_plus_gx_libretro.dll";
- }
-
+ std::string corePath = firelight::PlatformMetadata::getCoreDllPath(parent->platformId);
m_gameData = QByteArray(gameData.data(), gameData.size());
m_saveData = saveDataBytes;
m_corePath = QString::fromStdString(corePath);
} else {
- std::string corePath;
- if (entry->platformId == 7) {
- corePath = "./system/_cores/mupen64plus_next_libretro.dll";
- } else if (entry->platformId == 6) {
- corePath = "./system/_cores/snes9x_libretro.dll";
- } else if (entry->platformId == 2) {
- corePath = "./system/_cores/gambatte_libretro.dll";
- } else if (entry->platformId == 1) {
- corePath = "./system/_cores/gambatte_libretro.dll";
- } else if (entry->platformId == 3) {
- corePath = "./system/_cores/mgba_libretro.dll";
- } else if (entry->platformId == 10) {
- corePath = "./system/_cores/melondsds_libretro.dll";
- } else if (entry->platformId == 13) {
- corePath = "./system/_cores/genesis_plus_gx_libretro.dll";
- }
-
+ std::string corePath = firelight::PlatformMetadata::getCoreDllPath(entry->platformId);
auto size = std::filesystem::file_size(entry->contentPath);
QByteArray saveDataBytes;
@@ -560,34 +245,31 @@ void EmulationManager::loadLibraryEntry(int entryId) {
m_gameData = QByteArray(gameDataVec.data(), gameDataVec.size());
m_saveData = saveDataBytes;
m_corePath = QString::fromStdString(corePath);
- }
-
- m_core = std::make_unique(m_corePath.toStdString());
-
- m_core->setVideoReceiver(this);
- m_core->setAudioReceiver(new AudioManager());
- m_core->setRetropadProvider(getControllerManager());
- m_core->setSystemDirectory("./system");
- // m_core->setSaveDirectory(".");
- m_core->init();
-
- libretro::Game game(
- entry->contentPath,
- vector(m_gameData.begin(), m_gameData.end()));
- m_core->loadGame(&game);
-
- if (m_saveData.size() > 0) {
- m_core->writeMemoryData(libretro::SAVE_RAM,
- vector(m_saveData.begin(), m_saveData.end()));
+ m_gameReady = true;
}
- auto md5 = calculateMD5(m_gameData.data(), m_gameData.size());
- QMetaObject::invokeMethod(
- getAchievementManager(), "loadGame", Qt::QueuedConnection,
- Q_ARG(int, m_currentEntry.platformId),
- Q_ARG(QString, QString::fromStdString(entry->contentId)));
+ // m_core = std::make_unique(m_corePath.toStdString());
+ //
+ // m_core->setAudioReceiver(std::make_shared());
+ // m_core->setRetropadProvider(getControllerManager());
+ //
+ // m_core->setSystemDirectory("./system");
+ // // m_core->setSaveDirectory(".");
+ // m_core->init();
+ //
+ // libretro::Game game(
+ // entry->contentPath,
+ // vector(m_gameData.begin(), m_gameData.end()));
+ // m_core->loadGame(&game);
+ //
+ // if (m_saveData.size() > 0) {
+ // m_core->writeMemoryData(libretro::SAVE_RAM,
+ // vector(m_saveData.begin(), m_saveData.end()));
+ // }
+
+ // auto md5 = calculateMD5(m_gameData.data(), m_gameData.size());
- emit gameLoadSucceeded();
+ // emit gameLoadSucceeded();
});
}
diff --git a/src/app/emulation_manager.hpp b/src/app/emulation_manager.hpp
index ecf6114..8eeb1e5 100644
--- a/src/app/emulation_manager.hpp
+++ b/src/app/emulation_manager.hpp
@@ -9,143 +9,88 @@
#include
#include
+#include "libretro/core_configuration.hpp"
+
class EmulationManager : public QQuickFramebufferObject,
- public firelight::ManagerAccessor,
- public firelight::libretro::IVideoDataReceiver {
- Q_OBJECT
- Q_PROPERTY(QString currentGameName READ currentGameName NOTIFY
- currentGameNameChanged)
- Q_PROPERTY(int nativeWidth READ nativeWidth NOTIFY nativeWidthChanged)
- Q_PROPERTY(int nativeHeight READ nativeHeight NOTIFY nativeHeightChanged)
- Q_PROPERTY(float nativeAspectRatio READ nativeAspectRatio NOTIFY
- nativeAspectRatioChanged)
- Q_PROPERTY(bool running READ isRunning NOTIFY emulationStarted NOTIFY
- emulationStopped)
+ public firelight::ManagerAccessor {
+ Q_OBJECT
+ Q_PROPERTY(QString currentGameName READ currentGameName NOTIFY
+ currentGameNameChanged)
+ Q_PROPERTY(int nativeWidth READ nativeWidth NOTIFY nativeWidthChanged)
+ Q_PROPERTY(int nativeHeight READ nativeHeight NOTIFY nativeHeightChanged)
+ Q_PROPERTY(float nativeAspectRatio READ nativeAspectRatio NOTIFY
+ nativeAspectRatioChanged)
public:
- [[nodiscard]] Renderer *createRenderer() const override;
-
- explicit EmulationManager(QQuickItem *parent = nullptr);
-
- ~EmulationManager() override;
-
- void setCurrentFboId(int fboId);
-
- void
- setGetProcAddressFunction(const std::function
- &getProcAddressFunction);
-
- [[nodiscard]] std::function consumeContextResetFunction();
-
- [[nodiscard]] std::function consumeContextDestroyFunction();
+ [[nodiscard]] Renderer *createRenderer() const override;
- void setReceiveVideoDataFunction(
- const std::function
- &receiveVideoDataFunction);
+ explicit EmulationManager(QQuickItem *parent = nullptr);
- [[nodiscard]] QString currentGameName() const;
+ ~EmulationManager() override;
- [[nodiscard]] int nativeWidth() const;
+ [[nodiscard]] QString currentGameName() const;
- [[nodiscard]] int nativeHeight() const;
+ [[nodiscard]] int nativeWidth() const;
- [[nodiscard]] float nativeAspectRatio() const;
+ [[nodiscard]] int nativeHeight() const;
- void receive(const void *data, unsigned width, unsigned height,
- size_t pitch) override;
+ [[nodiscard]] float nativeAspectRatio() const;
- proc_address_t getProcAddress(const char *sym) override;
+ void setGeometry(int nativeWidth, int nativeHeight, float nativeAspectRatio);
- void setResetContextFunc(context_reset_func) override;
+ void setIsRunning(bool running);
- void setDestroyContextFunc(context_destroy_func) override;
+ QByteArray m_gameData;
+ QByteArray m_saveData;
+ QString m_corePath;
+ firelight::db::LibraryEntry m_currentEntry;
+ std::shared_ptr m_coreConfiguration = nullptr;
+ bool m_paused = false;
+ bool m_shouldReset = false;
- uintptr_t getCurrentFramebufferId() override;
+ bool m_running = false;
- void setSystemAVInfo(retro_system_av_info *info) override;
-
- [[nodiscard]] bool runFrame();
+ bool m_gameReady = false;
public slots:
- void loadLibraryEntry(int entryId);
-
- void startEmulation();
-
- void pauseGame();
+ void loadLibraryEntry(int entryId);
- void resumeGame();
+ void pauseGame();
- void stopEmulation();
+ void resumeGame();
- void resetEmulation();
-
- bool isRunning() const;
-
- void save(bool waitForFinish = false);
+ void resetEmulation();
signals:
- void gameLoadSucceeded();
-
- void gameLoadFailed();
+ void gameLoadSucceeded();
- void readyToStart();
+ void gameLoadFailed();
- void gamePaused();
+ void readyToStart();
- void gameResumed();
+ void gamePaused();
- void emulationStarted();
+ void gameResumed();
- void emulationStopped();
+ void emulationStarted();
- void currentGameNameChanged();
+ void emulationStopped();
- void nativeWidthChanged();
+ void currentGameNameChanged();
- void nativeHeightChanged();
+ void nativeWidthChanged();
- void nativeAspectRatioChanged();
+ void nativeHeightChanged();
- void loadAchievements(QString contentId);
+ void nativeAspectRatioChanged();
private:
- bool shouldUnload = false;
-
- std::unique_ptr m_core;
- firelight::db::LibraryEntry m_currentEntry;
-
- bool m_gameLoadedSignalReady = false;
- bool m_achievementsLoadedSignalReady = false;
-
- bool m_isRunning = false;
- bool m_paused = false;
-
- std::unique_ptr m_currentPlaySession;
- std::vector m_suspendPoints;
- QElapsedTimer m_playtimeTimer;
- QTimer m_autosaveTimer;
-
- QByteArray m_gameData;
- QByteArray m_saveData;
- QString m_corePath;
-
- int m_nativeWidth = 0;
- int m_nativeHeight = 0;
- float m_nativeAspectRatio = 0;
-
- int numFramesRun = 0;
-
- /****************************************************************************
- * Ugly rendering stuff
- ***************************************************************************/
- std::function m_getProcAddressFunction =
- nullptr;
- std::function m_resetContextFunction = nullptr;
- std::function m_destroyContextFunction = nullptr;
- std::function
- m_receiveVideoDataFunction = nullptr;
- bool m_usingHwRendering = false;
- int m_currentFboId = -1;
+ std::unique_ptr m_currentPlaySession;
+ std::vector m_suspendPoints;
+ QElapsedTimer m_playtimeTimer;
+ QTimer m_autosaveTimer;
+
+ int m_nativeWidth = 0;
+ int m_nativeHeight = 0;
+ float m_nativeAspectRatio = 0;
};
diff --git a/src/app/emulator_config_manager.cpp b/src/app/emulator_config_manager.cpp
new file mode 100644
index 0000000..a7d2fd8
--- /dev/null
+++ b/src/app/emulator_config_manager.cpp
@@ -0,0 +1,61 @@
+#include "emulator_config_manager.hpp"
+#include "platform_metadata.hpp"
+
+#include
+
+static QString thing = "disabled";
+
+EmulatorConfigManager::EmulatorConfigManager(firelight::db::IUserdataDatabase &userdataDatabase) : m_userdataDatabase(
+ userdataDatabase) {
+}
+
+EmulatorConfigManager::~EmulatorConfigManager() = default;
+
+std::shared_ptr EmulatorConfigManager::getCoreConfigFor(const int platformId, const int entryId) {
+ const auto key = std::to_string(platformId) + "_" + std::to_string(entryId);
+
+ if (!m_coreConfigs.contains(key)) {
+ m_coreConfigs[key] = std::make_shared();
+ }
+
+ // m_coreConfigs[key] = std::make_shared();
+
+ const auto allSettings = m_userdataDatabase.getAllPlatformSettings(platformId);
+ for (const auto &setting: allSettings) {
+ m_coreConfigs[key]->setPlatformValue(setting.first, setting.second);
+ }
+
+ return m_coreConfigs.at(key);
+}
+
+void EmulatorConfigManager::setOptionValueForPlatform(const int platformId, const QString &key, const QString &value) {
+ printf("Setting PLATFORM option %s to %s\n", key.toStdString().c_str(), value.toStdString().c_str());
+ m_userdataDatabase.setPlatformSettingValue(platformId, key.toStdString(), value.toStdString());
+}
+
+void EmulatorConfigManager::setOptionValueForEntry(int entryId, const QString &key, const QString &value) {
+ printf("Changing ENTRY option %s to %s\n", key.toStdString().c_str(), value.toStdString().c_str());
+}
+
+QString EmulatorConfigManager::getOptionValueForPlatform(const int platformId, const QString &key) {
+ printf("Getting PLATFORM option %s\n", key.toStdString().c_str());
+
+ auto val = m_userdataDatabase.getPlatformSettingValue(platformId, key.toStdString());
+ if (!val.has_value()) {
+ val = firelight::PlatformMetadata::getDefaultConfigValue(platformId, key.toStdString());
+ }
+
+ if (val.has_value()) {
+ printf("Found value (%s)!\n", val.value().c_str());
+ return QString::fromStdString(val.value());
+ }
+
+ printf("No value\n");
+ return "";
+}
+
+QString EmulatorConfigManager::getOptionValueForEntry(int entryId, const QString &key) {
+ printf("Getting ENTRY option %s\n", key.toStdString().c_str());
+ return "";
+}
+
diff --git a/src/app/emulator_config_manager.hpp b/src/app/emulator_config_manager.hpp
new file mode 100644
index 0000000..55fa8ff
--- /dev/null
+++ b/src/app/emulator_config_manager.hpp
@@ -0,0 +1,29 @@
+#pragma once
+
+#include
+#include
+
+#include "libretro/core_configuration.hpp"
+
+class EmulatorConfigManager : public QObject {
+ Q_OBJECT
+
+public:
+ explicit EmulatorConfigManager(firelight::db::IUserdataDatabase &userdataDatabase);
+
+ ~EmulatorConfigManager() override;
+
+ [[nodiscard]] std::shared_ptr getCoreConfigFor(int platformId, int entryId);
+
+ Q_INVOKABLE void setOptionValueForPlatform(int platformId, const QString &key, const QString &value);
+
+ Q_INVOKABLE void setOptionValueForEntry(int entryId, const QString &key, const QString &value);
+
+ Q_INVOKABLE QString getOptionValueForPlatform(int platformId, const QString &key);
+
+ Q_INVOKABLE QString getOptionValueForEntry(int entryId, const QString &key);
+
+private:
+ firelight::db::IUserdataDatabase &m_userdataDatabase;
+ std::map > m_coreConfigs;
+};
diff --git a/src/app/emulator_renderer.cpp b/src/app/emulator_renderer.cpp
index 3f1a5ce..e2a121d 100644
--- a/src/app/emulator_renderer.cpp
+++ b/src/app/emulator_renderer.cpp
@@ -8,41 +8,130 @@
#include
#include
-EmulatorRenderer::EmulatorRenderer(const EmulationManager *manager) {
+#include "audio_manager.hpp"
+
+static constexpr int AUTOSAVE_INTERVAL_MILLIS = 10000;
+
+EmulatorRenderer::EmulatorRenderer() {
initializeOpenGLFunctions();
- m_manager = const_cast(manager);
- m_manager->setReceiveVideoDataFunction(
- [this](const void *data, unsigned width, unsigned height, size_t pitch) {
- receiveVideoData(data, width, height, pitch);
- });
-
- m_manager->setGetProcAddressFunction([this](const char *sym) {
- return QOpenGLContext::currentContext()->getProcAddress(sym);
- });
+ autosaveTimer.setSingleShot(false);
+ autosaveTimer.setInterval(AUTOSAVE_INTERVAL_MILLIS);
+ QObject::connect(&autosaveTimer, &QTimer::timeout,
+ [this] {
+ m_shouldSave = true;
+ }
+ );
+}
+
+EmulatorRenderer::~EmulatorRenderer() {
+ autosaveTimer.stop();
+ getAchievementManager()->unloadGame();
+
+ save(true);
+ // TODO: SAVE
+
+ if (m_destroyContextFunction) {
+ m_destroyContextFunction();
+ }
+}
+
+proc_address_t EmulatorRenderer::getProcAddress(const char *sym) {
+ return QOpenGLContext::currentContext()->getProcAddress(sym);
+}
+
+void EmulatorRenderer::setResetContextFunc(context_reset_func func) {
+ m_usingHwRendering = true;
+ m_resetContextFunction = func;
+}
+
+void EmulatorRenderer::setDestroyContextFunc(context_destroy_func func) {
+ m_usingHwRendering = true;
+ m_destroyContextFunction = func;
+}
+
+uintptr_t EmulatorRenderer::getCurrentFramebufferId() {
+ if (!m_fbo) {
+ return -1;
+ }
+
+ return m_fbo->handle();
+}
+
+void EmulatorRenderer::setSystemAVInfo(retro_system_av_info *info) {
+ invalidateFramebufferObject();
+
+ const auto width = info->geometry.base_width;
+ const auto height = info->geometry.base_height;
+
+ if (width > 0 && height > 0) {
+ m_nativeWidth = width;
+ m_nativeHeight = height;
+
+ const auto aspectRatio =
+ static_cast(width) / static_cast(height);
+ m_nativeAspectRatio = aspectRatio;
+ }
+
+ update();
+}
+
+void EmulatorRenderer::setPixelFormat(retro_pixel_format *format) {
+ switch (*format) {
+ case RETRO_PIXEL_FORMAT_0RGB1555:
+ printf("Pixel format: 0RGB1555\n");
+ break;
+ case RETRO_PIXEL_FORMAT_XRGB8888:
+ m_pixelFormat = QImage::Format_RGB32;
+ break;
+ case RETRO_PIXEL_FORMAT_RGB565:
+ m_pixelFormat = QImage::Format_RGB16;
+ break;
+ case RETRO_PIXEL_FORMAT_UNKNOWN:
+ printf("Pixel format: UNKNOWN\n");
+ break;
+ }
+}
+
+void EmulatorRenderer::save(const bool waitForFinish) {
+ spdlog::debug("Saving game data\n");
+ firelight::saves::Savefile saveData(
+ m_core->getMemoryData(libretro::SAVE_RAM));
+ // saveData.setImage(m_fbo->toImage());
+
+ QFuture result =
+ getSaveManager()->writeSaveDataForEntry(m_currentEntry, saveData);
+
+ if (waitForFinish) {
+ result.waitForFinished();
+ }
}
void EmulatorRenderer::synchronize(QQuickFramebufferObject *fbo) {
const auto manager = reinterpret_cast(fbo);
- if (m_fbo) {
- manager->setCurrentFboId(m_fbo->handle());
- }
+ m_paused = manager->m_paused;
+ m_gameReady = manager->m_gameReady;
- if (manager->nativeWidth() != m_nativeWidth) {
- invalidateFramebufferObject();
- m_runAFrame = false;
+ if (manager->m_shouldReset) {
+ if (m_core) {
+ m_core->reset();
+ }
+ manager->m_shouldReset = false;
}
- if (!m_resetContextFunction) {
- m_resetContextFunction = manager->consumeContextResetFunction();
+ if (!m_core && m_gameReady) {
+ m_gameData = manager->m_gameData;
+ m_saveData = manager->m_saveData;
+ m_corePath = manager->m_corePath;
+ m_currentEntry = manager->m_currentEntry;
}
- // if (!m_destroyContextFunction) {
- // m_destroyContextFunction = manager->consumeContextDestroyFunction();
- // }
+ if (m_coreConfiguration) {
+ manager->m_coreConfiguration = m_coreConfiguration;
+ }
- m_nativeWidth = manager->nativeWidth();
- m_nativeHeight = manager->nativeHeight();
+ manager->setGeometry(m_nativeWidth, m_nativeHeight, m_nativeAspectRatio);
+ manager->setIsRunning(m_running);
Renderer::synchronize(fbo);
}
@@ -50,43 +139,86 @@ void EmulatorRenderer::synchronize(QQuickFramebufferObject *fbo) {
QOpenGLFramebufferObject *
EmulatorRenderer::createFramebufferObject(const QSize &size) {
if (m_nativeWidth != 0 && m_nativeHeight != 0) {
+ m_fboIsNew = true;
m_fbo =
Renderer::createFramebufferObject(QSize(m_nativeWidth, m_nativeHeight));
+
+ if (m_resetContextFunction) {
+ m_resetContextFunction();
+ }
return m_fbo;
}
m_fbo = Renderer::createFramebufferObject(size);
-
return m_fbo;
}
void EmulatorRenderer::render() {
- if (m_resetContextFunction) {
- m_resetContextFunction();
- m_resetContextFunction = nullptr;
- }
+ if (!m_core && m_gameReady) {
+ auto configProvider = getEmulatorConfigManager()->getCoreConfigFor(m_currentEntry.platformId, m_currentEntry.id);
+ m_core = std::make_unique(m_corePath.toStdString(), configProvider);
+
+ m_core->setVideoReceiver(this);
+ m_core->setAudioReceiver(std::make_shared());
+ m_core->setRetropadProvider(getControllerManager());
+
+ m_core->setSystemDirectory("./system");
+ // m_core->setSaveDirectory(".");
+ m_core->init();
+
+ libretro::Game game(
+ m_currentEntry.contentPath,
+ vector(m_gameData.begin(), m_gameData.end()));
+ m_core->loadGame(&game);
+
+ if (m_saveData.size() > 0) {
+ m_core->writeMemoryData(libretro::SAVE_RAM,
+ vector(m_saveData.begin(), m_saveData.end()));
+ }
+
+ autosaveTimer.start();
+
+ QMetaObject::invokeMethod(
+ getAchievementManager(), "loadGame", Qt::QueuedConnection,
+ Q_ARG(int, m_currentEntry.platformId),
+ Q_ARG(QString, QString::fromStdString(m_currentEntry.contentId)));
- if (m_manager->runFrame()) {
update();
- m_runAFrame = true;
- } else if (!m_runAFrame) {
- m_fbo->bind();
- glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
- glClear(GL_COLOR_BUFFER_BIT);
- m_fbo->release();
+ return;
}
+
+ if (m_fbo && m_core && !m_paused) {
+ m_running = true;
+ m_core->run(0);
+ getAchievementManager()->doFrame(m_core.get(), m_currentEntry);
+
+ if (m_shouldSave) {
+ save(false);
+ m_shouldSave = false;
+ }
+ } else if (m_fboIsNew) {
+ // m_fbo->bind();
+ // glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ // glClear(GL_COLOR_BUFFER_BIT);
+ // m_fbo->release();
+ }
+
+ update();
}
-void EmulatorRenderer::receiveVideoData(const void *data, unsigned width,
- unsigned height, size_t pitch) const {
- QOpenGLPaintDevice paint_device;
- paint_device.setSize(m_fbo->size());
- QPainter painter(&paint_device);
+void EmulatorRenderer::receive(const void *data, unsigned width,
+ unsigned height, size_t pitch) {
+ if (!m_usingHwRendering && data != nullptr) {
+ QOpenGLPaintDevice paint_device;
+ paint_device.setSize(m_fbo->size());
+ QPainter painter(&paint_device);
- m_fbo->bind();
- const QImage image((uchar *)data, width, height, pitch, QImage::Format_RGB16);
+ m_fbo->bind();
+ const QImage image((uchar *) data, width, height, pitch, m_pixelFormat);
- painter.drawImage(QRect(0, 0, m_fbo->width(), m_fbo->height()), image,
- image.rect());
- m_fbo->release();
+ // TODO: Check native size, etc. make sure we use max size and base size correctly
+ painter.drawImage(QRect(0, 0, m_fbo->width(), m_fbo->height()), image,
+ image.rect());
+ m_fbo->release();
+ }
}
diff --git a/src/app/emulator_renderer.hpp b/src/app/emulator_renderer.hpp
index 7c235f0..f04207e 100644
--- a/src/app/emulator_renderer.hpp
+++ b/src/app/emulator_renderer.hpp
@@ -7,31 +7,70 @@
#include
#include
+#include "libretro/core_configuration.hpp"
+
class EmulatorRenderer final : public QQuickFramebufferObject::Renderer,
public QOpenGLFunctions,
- public firelight::ManagerAccessor {
+ public firelight::ManagerAccessor,
+ public firelight::libretro::IVideoDataReceiver {
public:
- explicit EmulatorRenderer(const EmulationManager *manager);
+ explicit EmulatorRenderer();
+
+ void receive(const void *data, unsigned width, unsigned height, size_t pitch) override;
+
+ proc_address_t getProcAddress(const char *sym) override;
+
+ void setResetContextFunc(context_reset_func) override;
+
+ void setDestroyContextFunc(context_destroy_func) override;
+
+ uintptr_t getCurrentFramebufferId() override;
+
+ void setSystemAVInfo(retro_system_av_info *info) override;
+
+ void setPixelFormat(retro_pixel_format *format) override;
protected:
- void synchronize(QQuickFramebufferObject *fbo) override;
+ ~EmulatorRenderer() override;
- QOpenGLFramebufferObject *createFramebufferObject(const QSize &size) override;
+ void synchronize(QQuickFramebufferObject *fbo) override;
- void render() override;
+ QOpenGLFramebufferObject *createFramebufferObject(const QSize &size) override;
+
+ void render() override;
private:
- EmulationManager *m_manager = nullptr;
- QOpenGLFramebufferObject *m_fbo = nullptr;
+ void save(bool waitForFinish = false);
+
+ std::unique_ptr m_core = nullptr;
+ std::shared_ptr m_coreConfiguration = nullptr;
+
+ QOpenGLFramebufferObject *m_fbo = nullptr;
+ bool m_fboIsNew = true;
+
+ QByteArray m_gameData;
+ QByteArray m_saveData;
+ QString m_corePath;
+ firelight::db::LibraryEntry m_currentEntry;
+
+ // Default according to libretro docs
+ QImage::Format m_pixelFormat = QImage::Format_RGB16;
+
+ bool m_paused = false;
+ bool m_gameReady = false;
+
+ bool m_running = false;
- bool m_runAFrame = false;
+ bool m_usingHwRendering = false;
- int m_nativeWidth = 0;
- int m_nativeHeight = 0;
+ QTimer autosaveTimer;
+ bool m_shouldSave = false;
+ bool m_runAFrame = false;
- void receiveVideoData(const void *data, unsigned width, unsigned height,
- size_t pitch) const;
+ int m_nativeWidth = 0;
+ int m_nativeHeight = 0;
+ float m_nativeAspectRatio = 0.0f;
- std::function m_resetContextFunction = nullptr;
- std::function m_destroyContextFunction = nullptr;
+ std::function m_resetContextFunction = nullptr;
+ std::function m_destroyContextFunction = nullptr;
};
diff --git a/src/app/fps_multiplier.cpp b/src/app/fps_multiplier.cpp
deleted file mode 100644
index e188618..0000000
--- a/src/app/fps_multiplier.cpp
+++ /dev/null
@@ -1,18 +0,0 @@
-//
-// Created by nicho on 12/27/2023.
-//
-
-#include "fps_multiplier.hpp"
-
-void FpsMultiplier::stop() { std::printf("Stop!"); }
-
-void FpsMultiplier::start() { std::printf("Start!"); }
-
-void FpsMultiplier::setSliderValue(double value) {
- if (value == m_sliderValue) {
- return;
- }
- std::printf("Changed value from: %f to %f \r\n", m_sliderValue, value);
- m_sliderValue = value;
- emit sliderValueChanged(m_sliderValue);
-}
\ No newline at end of file
diff --git a/src/app/fps_multiplier.hpp b/src/app/fps_multiplier.hpp
deleted file mode 100644
index 3f4cdfd..0000000
--- a/src/app/fps_multiplier.hpp
+++ /dev/null
@@ -1,33 +0,0 @@
-//
-// Created by nicho on 12/27/2023.
-//
-
-#ifndef FPS_MULTIPLIER_H
-#define FPS_MULTIPLIER_H
-#include
-#include
-#include
-#include
-#include
-
-class FpsMultiplier : public QObject {
- Q_OBJECT
- Q_PROPERTY(double sliderValue READ sliderValue WRITE setSliderValue NOTIFY
- sliderValueChanged)
-
-public:
- inline double sliderValue() const { return m_sliderValue; }
-
-signals:
- void sliderValueChanged(double newValue);
-
-public slots:
- void stop();
- void start();
- void setSliderValue(double);
-
-private:
- double m_sliderValue = 1;
-};
-
-#endif // FPS_MULTIPLIER_H
diff --git a/src/app/input/controller_manager.cpp b/src/app/input/controller_manager.cpp
index e615faf..1fa4ec0 100644
--- a/src/app/input/controller_manager.cpp
+++ b/src/app/input/controller_manager.cpp
@@ -45,7 +45,7 @@ namespace firelight::Input {
}
std::optional
- ControllerManager::getRetropadForPlayer(const int t_player) {
+ ControllerManager::getRetropadForPlayerIndex(const int t_player) {
return getControllerForPlayer(t_player);
}
diff --git a/src/app/input/controller_manager.hpp b/src/app/input/controller_manager.hpp
index 78ceb89..087a47e 100644
--- a/src/app/input/controller_manager.hpp
+++ b/src/app/input/controller_manager.hpp
@@ -20,7 +20,7 @@ namespace firelight::Input {
getControllerForPlayer(int t_player) const;
std::optional
- getRetropadForPlayer(int t_player) override;
+ getRetropadForPlayerIndex(int t_player) override;
Q_INVOKABLE void updateControllerOrder(const QVector &order);
diff --git a/src/app/input/sdl_event_loop.cpp b/src/app/input/sdl_event_loop.cpp
index bdf2f07..77f903e 100644
--- a/src/app/input/sdl_event_loop.cpp
+++ b/src/app/input/sdl_event_loop.cpp
@@ -5,13 +5,15 @@
#include "sdl_event_loop.hpp"
#define SDL_MAIN_HANDLED
+#include
+#include
#include
#include
#include
namespace firelight {
- SdlEventLoop::SdlEventLoop(Input::ControllerManager *manager)
- : m_controllerManager(manager) {
+ SdlEventLoop::SdlEventLoop(QObject *window, Input::ControllerManager *manager)
+ : m_window(window), m_controllerManager(manager) {
SDL_SetHint(SDL_HINT_APP_NAME, "Firelight");
SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0");
@@ -54,14 +56,53 @@ namespace firelight {
m_controllerManager->handleSDLControllerEvent(ev);
break;
case SDL_CONTROLLERAXISMOTION:
- if (ev.caxis.value > 15000 || ev.caxis.value < -15000) {
- printf("axis: %d, value: %d\n", ev.caxis.axis, ev.caxis.value);
+ break;
+ case SDL_CONTROLLERBUTTONUP: {
+ Qt::Key key;
+
+ auto button = ev.cbutton.button;
+ if (button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT) {
+ key = Qt::Key_Right;
+ } else if (button == SDL_CONTROLLER_BUTTON_DPAD_LEFT) {
+ key = Qt::Key_Left;
+ } else if (button == SDL_CONTROLLER_BUTTON_DPAD_UP) {
+ key = Qt::Key_Up;
+ } else if (button == SDL_CONTROLLER_BUTTON_DPAD_DOWN) {
+ key = Qt::Key_Down;
+ } else if (button == SDL_CONTROLLER_BUTTON_X) {
+ key = Qt::Key_Menu;
+ } else {
+ break;
}
+
+ QApplication::postEvent(
+ m_window, new QKeyEvent(QEvent::KeyRelease, key, Qt::KeyboardModifier::NoModifier));
break;
- case SDL_CONTROLLERBUTTONUP:
- case SDL_CONTROLLERBUTTONDOWN:
- printf("button: %d, state: %d\n", ev.cbutton.button, ev.cbutton.state);
+ }
+ case SDL_CONTROLLERBUTTONDOWN: {
+ Qt::Key key;
+
+ auto button = ev.cbutton.button;
+ if (button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT) {
+ key = Qt::Key_Right;
+ } else if (button == SDL_CONTROLLER_BUTTON_DPAD_LEFT) {
+ key = Qt::Key_Left;
+ } else if (button == SDL_CONTROLLER_BUTTON_DPAD_UP) {
+ key = Qt::Key_Up;
+ } else if (button == SDL_CONTROLLER_BUTTON_DPAD_DOWN) {
+ key = Qt::Key_Down;
+ } else if (button == SDL_CONTROLLER_BUTTON_X) {
+ key = Qt::Key_Menu;
+ } else {
+ break;
+ }
+
+ QApplication::postEvent(
+ m_window, new QKeyEvent(QEvent::KeyPress, key, Qt::KeyboardModifier::NoModifier));
break;
+ }
+ // printf("button: %d, state: %d\n", ev.cbutton.button, ev.cbutton.state);
+ // break;
case SDL_JOYAXISMOTION:
case SDL_JOYBUTTONUP:
case SDL_JOYBUTTONDOWN:
diff --git a/src/app/input/sdl_event_loop.hpp b/src/app/input/sdl_event_loop.hpp
index 73a4eb1..7d2c076 100644
--- a/src/app/input/sdl_event_loop.hpp
+++ b/src/app/input/sdl_event_loop.hpp
@@ -5,20 +5,22 @@
#include
namespace firelight {
+ class SdlEventLoop final : public QThread {
+ public:
+ explicit SdlEventLoop(QObject *window, Input::ControllerManager *manager);
-class SdlEventLoop final : public QThread {
-public:
- explicit SdlEventLoop(Input::ControllerManager *manager);
- ~SdlEventLoop() override;
- void stopProcessing();
+ ~SdlEventLoop() override;
-protected:
- void run() override;
+ void stopProcessing();
-private:
- void processEvents() const;
- bool m_running = true;
- Input::ControllerManager *m_controllerManager;
-};
+ protected:
+ void run() override;
+ private:
+ void processEvents() const;
+
+ QObject *m_window;
+ bool m_running = true;
+ Input::ControllerManager *m_controllerManager;
+ };
} // namespace firelight
diff --git a/src/app/library/CMakeLists.txt b/src/app/library/CMakeLists.txt
index 43ccfe1..3c92e4c 100644
--- a/src/app/library/CMakeLists.txt
+++ b/src/app/library/CMakeLists.txt
@@ -7,4 +7,4 @@ set(LIBRARY_SOURCES
add_library(library ${LIBRARY_SOURCES})
-target_link_libraries(library PUBLIC Qt6::Concurrent Qt6::Sql spdlog::spdlog fmt::fmt ssl crypto)
\ No newline at end of file
+target_link_libraries(library PUBLIC Qt6::Concurrent Qt6::Sql spdlog::spdlog fmt::fmt ${OPENSSL_LIBRARIES})
diff --git a/src/app/library/library_scanner.cpp b/src/app/library/library_scanner.cpp
index efb5555..4965135 100644
--- a/src/app/library/library_scanner.cpp
+++ b/src/app/library/library_scanner.cpp
@@ -13,9 +13,9 @@
constexpr int MAX_FILESIZE_BYTES = 750000000;
LibraryScanner::LibraryScanner(
- firelight::db::ILibraryDatabase *lib_database,
- firelight::db::IContentDatabase *content_database)
- : library_database_(lib_database), content_database_(content_database) {
+ firelight::db::ILibraryDatabase *lib_database,
+ firelight::db::IContentDatabase *content_database)
+ : library_database_(lib_database), content_database_(content_database) {
scanner_thread_pool_ = std::make_unique();
scanner_thread_pool_->setMaxThreadCount(thread_pool_size_);
// directory_watcher_.addPath(
@@ -54,8 +54,8 @@ void LibraryScanner::startScan() {
ScanResults scan_results;
auto paths = library_database_->getAllLibraryContentDirectories();
- for (const auto &path : paths) {
- for (const auto &entry :
+ for (const auto &path: paths) {
+ for (const auto &entry:
std::filesystem::recursive_directory_iterator(path.path)) {
if (entry.is_directory()) {
continue;
@@ -73,7 +73,7 @@ void LibraryScanner::startScan() {
scan_results.all_filenames.emplace_back(filename);
auto existing = library_database_->getMatchingLibraryEntries(
- firelight::db::LibraryEntry{.contentPath = filename});
+ firelight::db::LibraryEntry{.contentPath = filename});
if (!existing.empty()) {
spdlog::debug("Found library entry with filename {}; skipping",
filename);
@@ -87,9 +87,9 @@ void LibraryScanner::startScan() {
// Check against content database
if (auto ext = entry.path().extension();
- ext.string() == ".mod" || ext.string() == ".ips" ||
- ext.string() == ".ups" || ext.string() == ".bps" ||
- ext.string() == ".ups") {
+ ext.string() == ".mod" || ext.string() == ".ips" ||
+ ext.string() == ".ups" || ext.string() == ".bps" ||
+ ext.string() == ".ups") {
// std::vector contents(filesize);
// std::ifstream file(entry.path(), std::ios::binary);
//
@@ -153,9 +153,10 @@ void LibraryScanner::startScan() {
} else if (ext.string() == ".smc" || ext.string() == ".n64" ||
ext.string() == ".v64" || ext.string() == ".z64" ||
ext.string() == ".gb" || ext.string() == ".gbc" ||
- ext.string() == ".gba" || ext.string() == ".sfc") {
+ ext.string() == ".gba" || ext.string() == ".sfc" ||
+ ext.string() == ".nes") {
auto platforms = content_database_->getMatchingPlatforms(
- {.supportedExtensions = {ext.string()}});
+ {.supportedExtensions = {ext.string()}});
if (platforms.empty()) {
printf("File extension not recognized: %s\n",
@@ -188,20 +189,31 @@ void LibraryScanner::startScan() {
// std::copy(thing.begin() + 512, thing.end(),
// new_thing.begin());
}
+ } else if (ext == ".nes") {
+ auto firstFourAsString = std::string(thing.begin(), thing.begin() + 4);
+ if (firstFourAsString == "NES\x1A") {
+ printf("FOUND HEADER!!! %s\n",
+ entry.path().filename().string().c_str());
+ thing.erase(thing.begin(), thing.begin() + 16);
+ filesize -= 16;
+
+ contentId = calculateMD5(thing.data(), filesize);
+ }
}
auto display_name = entry.path().filename().string();
firelight::db::LibraryEntry e = {
- .displayName = display_name,
- .contentId = contentId,
- .platformId = platforms.at(0).id,
- .type = firelight::db::LibraryEntry::EntryType::ROM,
- .fileMd5 = md5,
- .fileCrc32 = md5, // TODO: Calculate CRC32
- .sourceDirectory =
- canonical(entry.path().parent_path()).string(),
- .contentPath = canonical(entry.path()).string()};
+ .displayName = display_name,
+ .contentId = contentId,
+ .platformId = platforms.at(0).id,
+ .type = firelight::db::LibraryEntry::EntryType::ROM,
+ .fileMd5 = md5,
+ .fileCrc32 = md5, // TODO: Calculate CRC32
+ .sourceDirectory =
+ canonical(entry.path().parent_path()).string(),
+ .contentPath = canonical(entry.path()).string()
+ };
auto roms =
content_database_->getMatchingRoms({.md5 = contentId});
@@ -222,17 +234,17 @@ void LibraryScanner::startScan() {
auto allPaths = library_database_->getAllContentPaths();
- for (const auto &path : allPaths) {
+ for (const auto &path: allPaths) {
if (std::ranges::find(scan_results.all_filenames, path) ==
scan_results.all_filenames.end()) {
auto matchingEntry = library_database_->getMatchingLibraryEntries(
- {.contentPath = path});
+ {.contentPath = path});
library_database_->deleteLibraryEntry(matchingEntry.at(0).id);
}
}
- for (auto &new_entry : scan_results.new_entries) {
+ for (auto &new_entry: scan_results.new_entries) {
library_database_->createLibraryEntry(new_entry);
}
@@ -254,11 +266,12 @@ bool LibraryScanner::scanning() const { return scanning_; }
void LibraryScanner::refreshDirectories() {
auto dirs = library_database_->getAllLibraryContentDirectories();
- for (const auto &dir : dirs) {
+ for (const auto &dir: dirs) {
directory_watcher_.addPath(QString::fromStdString(dir.path));
}
}
void LibraryScanner::handleScannedPatchFile(
- const std::filesystem::directory_entry &entry,
- ScanResults &scan_results) const {}
+ const std::filesystem::directory_entry &entry,
+ ScanResults &scan_results) const {
+}
diff --git a/src/app/libretro/core.cpp b/src/app/libretro/core.cpp
index d8110bc..0a40fc3 100644
--- a/src/app/libretro/core.cpp
+++ b/src/app/libretro/core.cpp
@@ -3,6 +3,7 @@
#include "SDL2/SDL.h"
#include "virtual_filesystem.hpp"
#include
+#include
#include
@@ -31,7 +32,7 @@ namespace libretro {
}
const auto controllerOpt =
- currentCore->getRetropadProvider()->getRetropadForPlayer(port);
+ currentCore->getRetropadProvider()->getRetropadForPlayerIndex(port);
if (!controllerOpt.has_value()) {
return 0;
}
@@ -127,10 +128,9 @@ namespace libretro {
}
case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT: {
environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_PIXEL_FORMAT");
- auto ptr = (retro_pixel_format *) data;
- // TODO: Implement
- // video->setPixelFormat((retro_pixel_format *)data);
- return true;
+ auto ptr = static_cast(data);
+ videoReceiver->setPixelFormat(ptr);
+ break;
}
case RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS: {
environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS");
@@ -174,10 +174,12 @@ namespace libretro {
}
case RETRO_ENVIRONMENT_SET_HW_RENDER: {
environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_HW_RENDER");
- // TODO I think this is actually mostly stuff informing the frontend
- printf("Setting hw render\n");
auto *renderCallback = static_cast(data);
+ renderCallback->context_type = RETRO_HW_CONTEXT_OPENGL_CORE;
+ renderCallback->version_major = 4;
+ renderCallback->version_minor = 1;
+
renderCallback->get_proc_address =
[](const char *sym) -> retro_proc_address_t {
return currentCore->videoReceiver->getProcAddress(sym);
@@ -187,75 +189,74 @@ namespace libretro {
return currentCore->videoReceiver->getCurrentFramebufferId();
};
- // printf("huh\n");
currentCore->videoReceiver->setResetContextFunc(
renderCallback->context_reset);
if (renderCallback->context_destroy) {
printf("context destroy is not null!\n");
- // currentCore->videoReceiver->setDestroyContextFunc(
- // renderCallback->context_destroy);
+ currentCore->videoReceiver->setDestroyContextFunc(
+ renderCallback->context_destroy);
currentCore->destroyContextFunction = renderCallback->context_destroy;
}
- // return false;
- return true;
+ break;
}
case RETRO_ENVIRONMENT_GET_VARIABLE: {
environmentCalls.emplace_back("RETRO_ENVIRONMENT_GET_VARIABLE");
auto ptr = static_cast(data);
- for (const auto &opt: options) {
- if (strcmp(opt.key, ptr->key) == 0) {
- // auto strr = "mupen64plus-pak1";
- // auto val = "rumble";
- // if (strcmp(opt.key, strr) == 0) {
- // ptr->value = val;
- // } else {
- // ptr->value = opt.currentValue;
- // }
- // auto strr = "mupen64plus-rsp-plugin";
- // auto val = "parallel";
- // if (strcmp(opt.key, strr) == 0) {
- // printf("Setting rsp plugin to %s\n", val);
- // ptr->value = val;
- // } else {
- // ptr->value = opt.currentValue;
- // }
-
- ptr->value = opt.currentValue;
-
- // auto strr2 = "mupen64plus-rdp-plugin";
- // auto val2 = "angrylion";
- // if (strcmp(opt.key, strr2) == 0) {
- // printf("Setting rdp plugin to %s\n", val2);
- // ptr->value = val2;
- // }
- return true;
- }
+
+ auto configProvider = currentCore->m_configurationProvider;
+ if (!configProvider) {
+ return false;
}
- return true;
+
+ auto val = configProvider->getOptionValue(ptr->key);
+ if (!val.has_value()) {
+ return false;
+ }
+
+ ptr->value = val->key.c_str();
+ break;
}
+
case RETRO_ENVIRONMENT_SET_VARIABLES: {
environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_VARIABLES");
auto ptr = static_cast(data);
- // TODO sane default
- for (int i = 0; i < 100; ++i) {
+
+ auto configProvider = currentCore->m_configurationProvider;
+ if (!configProvider) {
+ return false;
+ }
+
+ for (int i = 0; i < 200; ++i) {
auto opt = ptr[i];
- printf("Variable KEY: %s VALUE: %s\n", opt.key, opt.value);
if (opt.key == nullptr) {
break;
}
+
+ firelight::libretro::IConfigurationProvider::Option option;
+ option.key = opt.key;
+ option.label = opt.value;
+
+ configProvider->registerOption(option);
}
- return true;
+ break;
}
case RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE: {
environmentCalls.emplace_back("RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE");
- *static_cast(data) = false; // TODO: actually implement
- return true;
+
+ const auto configProvider = currentCore->m_configurationProvider;
+ if (!configProvider) {
+ *static_cast(data) = configProvider->anyOptionValueHasChanged();
+ } else {
+ *static_cast(data) = false;
+ }
+ break;
}
case RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME: {
environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME");
canRunWithNoGame = *static_cast(data);
+ break;
}
case RETRO_ENVIRONMENT_GET_LIBRETRO_PATH: {
environmentCalls.emplace_back("RETRO_ENVIRONMENT_GET_LIBRETRO_PATH");
@@ -284,7 +285,7 @@ namespace libretro {
ptr->set_rumble_state = [](unsigned port, enum retro_rumble_effect effect,
uint16_t strength) {
const auto con =
- currentCore->getRetropadProvider()->getRetropadForPlayer(port);
+ currentCore->getRetropadProvider()->getRetropadForPlayerIndex(port);
if (!con.has_value()) {
return true;
}
@@ -298,7 +299,7 @@ namespace libretro {
return true;
};
- return true;
+ break;
}
case RETRO_ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES: {
environmentCalls.emplace_back(
@@ -312,7 +313,7 @@ namespace libretro {
// RETRO_DEVICE_ANALOG).
*ptr = (1 << RETRO_DEVICE_JOYPAD) | (1 << RETRO_DEVICE_ANALOG);
- return false;
+ return true;
}
case RETRO_ENVIRONMENT_GET_SENSOR_INTERFACE: {
environmentCalls.emplace_back("RETRO_ENVIRONMENT_GET_SENSOR_INTERFACE");
@@ -432,13 +433,12 @@ namespace libretro {
case RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO: {
environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO");
videoReceiver->setSystemAVInfo(static_cast(data));
- // video->setGameGeometry(&retroSystemAVInfo->geometry);
return true;
}
- case RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK:
- environmentCalls.emplace_back(
- "RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK");
- break;
+ // case RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK:
+ // environmentCalls.emplace_back(
+ // "RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK");
+ // break;
case RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO: {
environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO");
auto ptr = static_cast(data);
@@ -458,17 +458,31 @@ namespace libretro {
case RETRO_ENVIRONMENT_SET_CONTROLLER_INFO: {
environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_CONTROLLER_INFO");
auto ptr = static_cast(data);
- for (unsigned i = 0; i < ptr->num_types; ++i) {
- auto info = ptr->types[i];
- if (info.desc == nullptr) {
+
+
+ for (unsigned i = 0; i < 100; ++i) {
+ auto info = ptr[i];
+
+ if (!info.types) {
break;
}
- controllerInfo.emplace_back(info);
- if (i == 100) {
- recordPotentialAPIViolation("Over 100 controller infos");
+ for (unsigned j = 0; j < info.num_types; ++j) {
+ auto type = info.types[j];
+ printf("Type: %d, Value: %s\n", type.id, type.desc);
}
}
+ // for (unsigned i = 0; i < ptr->num_types; ++i) {
+ // auto info = ptr->types[i];
+ // if (info.desc == nullptr) {
+ // break;
+ // }
+ //
+ // controllerInfo.emplace_back(info);
+ // if (i == 100) {
+ // recordPotentialAPIViolation("Over 100 controller infos");
+ // }
+ // }
return true;
}
case RETRO_ENVIRONMENT_SET_MEMORY_MAPS: {
@@ -519,16 +533,22 @@ namespace libretro {
auto ptr = static_cast(data);
ptr->interface_type = RETRO_HW_RENDER_INTERFACE_DUMMY;
ptr->interface_version = 0;
+ return true;
}
case RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS: {
environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS");
supportsAchievements = *static_cast(data);
return true;
}
- case RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE:
+ case RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE: {
environmentCalls.emplace_back(
"RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE");
+ auto ptr = static_cast(data);
+ auto type = ptr->interface_type;
+ auto version = ptr->interface_version;
+
break;
+ }
case RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS:
environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS");
break;
@@ -583,6 +603,7 @@ namespace libretro {
break;
case RETRO_ENVIRONMENT_GET_FASTFORWARDING: {
environmentCalls.emplace_back("RETRO_ENVIRONMENT_GET_FASTFORWARDING");
+ // TODO: Get from video provider?
auto ptr = static_cast(data);
*ptr = fastforwarding;
return true;
@@ -592,138 +613,319 @@ namespace libretro {
break;
case RETRO_ENVIRONMENT_GET_INPUT_BITMASKS:
environmentCalls.emplace_back("RETRO_ENVIRONMENT_GET_INPUT_BITMASKS");
- break;
+ // TODO: Implement
+ return false;
case RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION: {
environmentCalls.emplace_back("RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION");
- // TODO: Set this behind some user-settable flag?
auto ptr = static_cast(data);
*ptr = 2;
- return true;
+ break;
}
case RETRO_ENVIRONMENT_SET_CORE_OPTIONS: {
environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_CORE_OPTIONS");
auto ptr = static_cast(data);
- // TODO sane default
+
+ auto configProvider = currentCore->m_configurationProvider;
+ if (!configProvider) {
+ return false;
+ }
+
for (int i = 0; i < 200; ++i) {
- auto opt = *ptr[i];
- printf("OPTION KEY: %s\n", opt.key);
- if (opt.key == nullptr) {
+ auto opt = ptr[i];
+ if (opt->key == nullptr) {
break;
}
- // TODO: pointer?
- CoreOption coreOption(opt);
- options.emplace_back(coreOption);
+ firelight::libretro::IConfigurationProvider::Option option;
+ option.key = opt->key;
+ option.label = opt->desc;
+ option.description = opt->info;
+
+ if (opt->default_value != nullptr) {
+ option.defaultValueKey = opt->default_value;
+ } else {
+ option.defaultValueKey = opt->values[0].value;
+ }
+
+ for (int j = 0; j < 100; ++j) {
+ auto val = opt->values[j];
+ if (val.value == nullptr) {
+ break;
+ }
+
+ firelight::libretro::IConfigurationProvider::OptionValue optionValue;
+ optionValue.key = val.value;
+ if (val.label != nullptr) {
+ optionValue.label = val.label;
+ } else {
+ optionValue.label = val.value;
+ }
+
+ option.possibleValues.emplace_back(optionValue);
+ }
+
+ configProvider->registerOption(option);
}
- return true;
+ break;
}
case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL: {
environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL");
auto ptr = static_cast(data);
- // TODO sane default
+
+ auto configProvider = currentCore->m_configurationProvider;
+ if (!configProvider) {
+ return false;
+ }
+
for (int i = 0; i < 200; ++i) {
auto opt = ptr->us[i];
if (opt.key == nullptr) {
break;
}
- CoreOption coreOption(opt);
- options.emplace_back(coreOption);
+ firelight::libretro::IConfigurationProvider::Option option;
+ option.key = opt.key;
+ option.label = opt.desc;
+ option.description = opt.info;
+
+ if (opt.default_value != nullptr) {
+ option.defaultValueKey = opt.default_value;
+ } else {
+ option.defaultValueKey = opt.values[0].value;
+ }
+
+ for (int j = 0; j < 100; ++j) {
+ auto val = opt.values[j];
+ if (val.value == nullptr) {
+ break;
+ }
+
+ firelight::libretro::IConfigurationProvider::OptionValue optionValue;
+ optionValue.key = val.value;
+ if (val.label != nullptr) {
+ optionValue.label = val.label;
+ } else {
+ optionValue.label = val.value;
+ }
+
+ option.possibleValues.emplace_back(optionValue);
+ }
+
+ configProvider->registerOption(option);
}
- return true;
+
+
+ break;
}
case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY: {
environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY");
auto ptr = static_cast(data);
- for (auto opt: options) {
- if (strcmp(ptr->key, opt.key) == 0) {
- opt.displayToUser = ptr->visible;
- return true;
- }
+
+ auto configProvider = currentCore->m_configurationProvider;
+ if (!configProvider) {
+ return false;
}
- return false;
+
+ configProvider->setOptionVisibility(ptr->key, ptr->visible);
+ break;
}
case RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER:
environmentCalls.emplace_back("RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER");
*static_cast(data) = RETRO_HW_CONTEXT_OPENGL;
return true;
- case RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION:
- environmentCalls.emplace_back(
- "RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION");
- break;
- case RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE:
- environmentCalls.emplace_back(
- "RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE");
- break;
- case RETRO_ENVIRONMENT_GET_MESSAGE_INTERFACE_VERSION:
+ // case RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION:
+ // environmentCalls.emplace_back(
+ // "RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION");
+ // return false;
+ // case RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE:
+ // environmentCalls.emplace_back(
+ // "RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE");
+ // return false;
+ case RETRO_ENVIRONMENT_GET_MESSAGE_INTERFACE_VERSION: {
environmentCalls.emplace_back(
"RETRO_ENVIRONMENT_GET_MESSAGE_INTERFACE_VERSION");
+ auto ptr = static_cast(data);
+ *ptr = 1;
break;
- case RETRO_ENVIRONMENT_SET_MESSAGE_EXT:
+ }
+ case RETRO_ENVIRONMENT_SET_MESSAGE_EXT: {
environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_MESSAGE_EXT");
+ auto ptr = static_cast(data);
+
+ // TODO
+ printf("Msg: %s\n", ptr->msg);
break;
- case RETRO_ENVIRONMENT_GET_INPUT_MAX_USERS:
- environmentCalls.emplace_back("RETRO_ENVIRONMENT_GET_INPUT_MAX_USERS");
- break;
- case RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK:
+ }
+ // case RETRO_ENVIRONMENT_GET_INPUT_MAX_USERS:
+ // environmentCalls.emplace_back("RETRO_ENVIRONMENT_GET_INPUT_MAX_USERS");
+ // return false;
+ case RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK: {
environmentCalls.emplace_back(
"RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK");
+ if (!data) {
+ break;
+ }
+
+ auto ptr = static_cast(data);
+
+ ptr->callback = [](bool active, unsigned occupancy, bool underrun_likely) {
+ printf("Active: %d, Occupancy: %d, Underrun Likely: %d\n", active, occupancy, underrun_likely);
+ };
+
break;
- case RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY:
+ }
+ case RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY: {
environmentCalls.emplace_back(
"RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY");
+ // TODO
break;
- case RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE:
- environmentCalls.emplace_back(
- "RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE");
- break;
- case RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE:
+ }
+ // case RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE:
+ // environmentCalls.emplace_back(
+ // "RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE");
+ // break;
+ case RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE: {
environmentCalls.emplace_back(
"RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE");
- break;
- case RETRO_ENVIRONMENT_GET_GAME_INFO_EXT:
+ auto ptr = static_cast(data);
+ for (int i = 0; i < 100; ++i) {
+ auto info = ptr[i];
+ if (info.extensions == nullptr) {
+ break;
+ }
+ }
+ return false;
+ // break;
+ }
+ case RETRO_ENVIRONMENT_GET_GAME_INFO_EXT: {
environmentCalls.emplace_back("RETRO_ENVIRONMENT_GET_GAME_INFO_EXT");
- break;
+ auto ptr = static_cast(data);
+ return false;
+ // break;
+ }
case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2: {
environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2");
auto ptr = static_cast(data);
+
+ auto configProvider = currentCore->m_configurationProvider;
+ if (!configProvider) {
+ return false;
+ }
+
for (int i = 0; i < 100; ++i) {
auto opt = ptr->categories[i];
if (opt.key == nullptr) {
break;
}
}
+
for (int i = 0; i < 200; ++i) {
auto opt = ptr->definitions[i];
if (opt.key == nullptr) {
break;
}
- CoreOption coreOption(opt);
- options.emplace_back(coreOption);
+ firelight::libretro::IConfigurationProvider::Option option;
+ option.key = opt.key;
+
+ if (opt.default_value != nullptr) {
+ option.defaultValueKey = opt.default_value;
+ } else {
+ option.defaultValueKey = opt.values[0].value;
+ }
+
+ if (opt.desc_categorized != nullptr) {
+ option.label = opt.desc_categorized;
+ } else {
+ option.label = opt.desc;
+ }
+
+ if (opt.info_categorized != nullptr) {
+ option.description = opt.info_categorized;
+ } else {
+ option.description = opt.info;
+ }
+
+ for (int j = 0; j < 100; ++j) {
+ auto val = opt.values[j];
+ if (val.value == nullptr) {
+ break;
+ }
+
+ firelight::libretro::IConfigurationProvider::OptionValue optionValue;
+ optionValue.key = val.value;
+ if (val.label != nullptr) {
+ optionValue.label = val.label;
+ } else {
+ optionValue.label = val.value;
+ }
+
+ option.possibleValues.emplace_back(optionValue);
+ }
+
+ configProvider->registerOption(option);
}
- return true;
+ break;
}
case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL: {
environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL");
// TODO
auto ptr = static_cast(data);
- for (int i = 0; i < 100; ++i) {
- auto opt = ptr->us->categories[i];
- if (opt.key == nullptr) {
- break;
- }
+
+ auto configProvider = currentCore->m_configurationProvider;
+ if (!configProvider) {
+ return false;
}
+
for (int i = 0; i < 200; ++i) {
auto opt = ptr->us->definitions[i];
if (opt.key == nullptr) {
break;
}
- CoreOption coreOption(opt);
- options.emplace_back(coreOption);
+ firelight::libretro::IConfigurationProvider::Option option;
+ option.key = opt.key;
+
+ if (opt.default_value != nullptr) {
+ option.defaultValueKey = opt.default_value;
+ } else {
+ option.defaultValueKey = opt.values[0].value;
+ }
+
+ if (opt.desc_categorized != nullptr) {
+ option.label = opt.desc_categorized;
+ } else if (opt.desc != nullptr) {
+ option.label = opt.desc;
+ } else {
+ option.label = opt.key;
+ }
+
+ if (opt.info_categorized != nullptr) {
+ option.description = opt.info_categorized;
+ } else if (opt.info != nullptr) {
+ option.description = opt.info;
+ }
+
+ for (int j = 0; j < 100; ++j) {
+ auto val = opt.values[j];
+ if (val.value == nullptr) {
+ break;
+ }
+
+ firelight::libretro::IConfigurationProvider::OptionValue optionValue;
+ optionValue.key = val.value;
+ if (val.label != nullptr) {
+ optionValue.label = val.label;
+ } else {
+ optionValue.label = val.value;
+ }
+
+ option.possibleValues.emplace_back(optionValue);
+ }
+
+ configProvider->registerOption(option);
}
- return true;
+ break;
}
case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK: {
environmentCalls.emplace_back(
@@ -737,26 +939,8 @@ namespace libretro {
}
case RETRO_ENVIRONMENT_SET_VARIABLE: {
environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_VARIABLE");
- auto ptr = static_cast(data);
- if (ptr == nullptr) {
- return true;
- }
-
- for (auto opt: options) {
- if (strcmp(opt.key, ptr->key) == 0) {
- for (auto v: opt.values) {
- if (strcmp(ptr->value, v.value) == 0) {
- opt.currentValue = ptr->value;
- return true;
- }
- recordPotentialAPIViolation(
- "SET_VARIABLE with unknown value for key TODO");
- }
- // TODO: Make sure value is one of the allowed strings
- }
- }
-
- return true;
+ // TODO: Implement
+ break;
}
case RETRO_ENVIRONMENT_GET_THROTTLE_STATE: {
environmentCalls.emplace_back("RETRO_ENVIRONMENT_GET_THROTTLE_STATE");
@@ -828,8 +1012,10 @@ namespace libretro {
default:
printf("Unimplemented env command: %d\n", cmd);
environmentCalls.emplace_back("UNIMPLEMENTED");
+ return false;
}
- return false;
+
+ return true;
}
template
@@ -936,7 +1122,9 @@ namespace libretro {
// return j.dump();
// }
- Core::Core(const std::string &libPath) {
+ Core::Core(const std::string &libPath,
+ std::shared_ptr configProvider) : m_configurationProvider(
+ std::move(configProvider)) {
coreLib = std::make_unique(QString::fromStdString(libPath));
// dll = SDL_LoadObject(libPath.c_str());
@@ -956,7 +1144,7 @@ namespace libretro {
reinterpret_cast(
coreLib->resolve("retro_set_controller_port_device"));
symRetroReset = coreLib->resolve("retro_reset");
- symRetroRun = reinterpret_cast(coreLib->resolve("retro_run"));
+ symRetroRun = coreLib->resolve("retro_run");
symRetroSerializeSize =
reinterpret_cast(coreLib->resolve("retro_serialize_size"));
symRetroSerialize = reinterpret_cast(
@@ -964,7 +1152,7 @@ namespace libretro {
symRetroUnserialize = reinterpret_cast(
coreLib->resolve("retro_unserialize"));
symRetroCheatReset =
- reinterpret_cast(coreLib->resolve("retro_cheat_reset"));
+ coreLib->resolve("retro_cheat_reset");
symRetroCheatSet = reinterpret_cast(
coreLib->resolve("retro_cheat_set"));
@@ -974,7 +1162,7 @@ namespace libretro {
reinterpret_cast(
coreLib->resolve("retro_load_game_special"));
symRetroUnloadGame =
- reinterpret_cast(coreLib->resolve("retro_unload_game"));
+ coreLib->resolve("retro_unload_game");
symRetroGetRegion = reinterpret_cast(
coreLib->resolve("retro_get_region"));
@@ -1069,18 +1257,17 @@ namespace libretro {
// loadRetroFunc(dll, "retro_set_input_poll")([]() {});
// loadRetroFunc(dll,
// "retro_set_input_state")(inputStateCallback);
-
- symRetroSetControllerPortDevice(0, RETRO_DEVICE_ANALOG);
}
Core::~Core() {
+ printf("Destroying core\n");
// if (destroyContextFunction) {
// printf("Destroying context\n");
// destroyContextFunction();
// }
- // unloadGame();
- // deinit();
+ unloadGame();
+ deinit();
// SDL_UnloadObject(dll);
//
@@ -1113,12 +1300,6 @@ namespace libretro {
}
void Core::unloadGame() {
- if (destroyContextFunction) {
- printf("Destroying context\n");
- destroyContextFunction();
- destroyContextFunction = nullptr;
- }
-
symRetroUnloadGame();
}
@@ -1216,7 +1397,7 @@ namespace libretro {
return m_retropadProvider;
}
- void Core::setAudioReceiver(IAudioDataReceiver *receiver) {
- audioReceiver = receiver;
+ void Core::setAudioReceiver(std::shared_ptr receiver) {
+ audioReceiver = std::move(receiver);
}
} // namespace libretro
diff --git a/src/app/libretro/core.hpp b/src/app/libretro/core.hpp
index 3ce7c6b..1bf5f84 100644
--- a/src/app/libretro/core.hpp
+++ b/src/app/libretro/core.hpp
@@ -1,14 +1,13 @@
#pragma once
-#include "coreoption.hpp"
#include "firelight/libretro/audio_data_receiver.hpp"
#include "firelight/libretro/retropad_provider.hpp"
#include "firelight/libretro/video_data_receiver.hpp"
+#include "firelight/libretro/configuration_provider.hpp"
#include "game.hpp"
#include "libretro/libretro.h"
#include
-#include
#include
#include
@@ -17,188 +16,194 @@ using std::string;
using std::vector;
namespace libretro {
+ enum MemoryType {
+ SAVE_RAM = RETRO_MEMORY_SAVE_RAM,
+ RTC = RETRO_MEMORY_RTC,
+ SYSTEM_RAM = RETRO_MEMORY_SYSTEM_RAM,
+ VIDEO_RAM = RETRO_MEMORY_VIDEO_RAM
+ };
-enum MemoryType {
- SAVE_RAM = RETRO_MEMORY_SAVE_RAM,
- RTC = RETRO_MEMORY_RTC,
- SYSTEM_RAM = RETRO_MEMORY_SYSTEM_RAM,
- VIDEO_RAM = RETRO_MEMORY_VIDEO_RAM
-};
+ typedef void (*RetroSetEnvironment)(bool (*)(unsigned cmd, void *data));
-typedef void (*RetroSetEnvironment)(bool (*)(unsigned cmd, void *data));
-typedef void (*RetroSetVideoRefresh)(retro_video_refresh_t);
-typedef void (*RetroSetAudioSample)(retro_audio_sample_t);
-typedef void (*RetroSetAudioSampleBatch)(retro_audio_sample_batch_t);
-typedef void (*RetroInputState)(retro_input_state_t);
-typedef void (*RetroInputPoll)(retro_input_poll_t);
-typedef void (*RetroRunFunc)();
+ typedef void (*RetroSetVideoRefresh)(retro_video_refresh_t);
-class Core {
+ typedef void (*RetroSetAudioSample)(retro_audio_sample_t);
-public:
- std::basic_string dumpJson();
+ typedef void (*RetroSetAudioSampleBatch)(retro_audio_sample_batch_t);
- Core(const std::string &libPath);
+ typedef void (*RetroInputState)(retro_input_state_t);
- virtual ~Core();
+ typedef void (*RetroInputPoll)(retro_input_poll_t);
- void setVideoReceiver(firelight::libretro::IVideoDataReceiver *receiver);
- void setRetropadProvider(firelight::libretro::IRetropadProvider *provider);
- firelight::libretro::IRetropadProvider *getRetropadProvider() const;
+ typedef void (*RetroRunFunc)();
- void setAudioReceiver(IAudioDataReceiver *receiver);
+ class Core {
+ public:
+ std::basic_string dumpJson();
- bool handleEnvironmentCall(unsigned cmd, void *data);
+ Core(const std::string &libPath, std::shared_ptr configProvider);
- void init();
+ virtual ~Core();
- void deinit();
+ void setVideoReceiver(firelight::libretro::IVideoDataReceiver *receiver);
- void reset();
+ void setRetropadProvider(firelight::libretro::IRetropadProvider *provider);
- void run(double deltaTime);
+ firelight::libretro::IRetropadProvider *getRetropadProvider() const;
- bool loadGame(Game *game);
+ void setAudioReceiver(std::shared_ptr receiver);
- void unloadGame();
+ bool handleEnvironmentCall(unsigned cmd, void *data);
- std::vector serializeState() const;
- void deserializeState(const std::vector &data) const;
+ void init();
- size_t getSerializeSize() const;
+ void deinit();
- void setSystemDirectory(const string &);
+ void reset();
- void setSaveDirectory(const string &);
+ void run(double deltaTime);
- [[nodiscard]] std::vector getMemoryData(MemoryType memType) const;
+ bool loadGame(Game *game);
- void writeMemoryData(MemoryType memType, const std::vector &data);
- firelight::libretro::IVideoDataReceiver *videoReceiver;
+ void unloadGame();
- void *getMemoryData(unsigned id) const;
- size_t getMemorySize(unsigned id) const;
+ std::vector serializeState() const;
- retro_memory_map *getMemoryMap();
+ void deserializeState(const std::vector &data) const;
- std::function destroyContextFunction = nullptr;
+ size_t getSerializeSize() const;
-private:
- std::unique_ptr coreLib;
+ void setSystemDirectory(const string &);
- firelight::libretro::IRetropadProvider *m_retropadProvider;
- IAudioDataReceiver *audioReceiver;
+ void setSaveDirectory(const string &);
- retro_vfs_interface m_vfsInterface;
+ [[nodiscard]] std::vector getMemoryData(MemoryType memType) const;
- vector environmentCalls;
+ void writeMemoryData(MemoryType memType, const std::vector &data);
- retro_system_info *retroSystemInfo;
- retro_system_av_info *retroSystemAVInfo;
+ firelight::libretro::IVideoDataReceiver *videoReceiver;
- // Informational to frontend.
- bool canRunWithNoGame = false;
- unsigned performanceLevel = 0;
- bool supportsAchievements = false;
- bool shutdown = false;
- vector inputDescriptors;
+ void *getMemoryData(unsigned id) const;
- // Informational to core.
- string systemDirectory;
- string coreAssetsDirectory;
- string saveDirectory;
- string libretroPath;
- string username;
- unsigned frontendLanguage;
- bool isJITCapable;
+ size_t getMemorySize(unsigned id) const;
- retro_disk_control_callback *diskControlCallback;
- unsigned diskControlInterfaceVersion;
- retro_disk_control_ext_callback *diskControlExtCallback;
- retro_rumble_interface *rumbleInterface;
- uint64_t serializationQuirksBitmap;
- retro_vfs_interface_info *virtualFileSystemInterfaceInfo;
- retro_led_interface *ledInterface;
- unsigned messageInterfaceVersion;
- retro_message_ext *messageExt; // todo
- retro_fastforwarding_override *fastforwardingOverride;
- retro_system_content_info_override *contentInfoOverride;
- retro_game_info_ext *gameInfoExt;
- retro_throttle_state *throttleState;
- int saveStateContext;
- retro_microphone_interface *microphoneInterface;
- retro_netpacket_callback *netpacketCallback;
- retro_device_power *devicePower;
- bool fastforwarding;
+ retro_memory_map *getMemoryMap();
- unsigned coreOptionsVersion;
- std::vector options;
+ std::function destroyContextFunction = nullptr;
- retro_sensor_interface *sensorInterface;
- retro_camera_callback *cameraCallback;
- retro_log_callback *logCallback;
- retro_perf_callback *performanceCallback;
- retro_location_callback *locationCallback;
- retro_get_proc_address_interface *procAddressCallback;
- vector subsystemInfo;
- vector memoryDescriptors;
+ private:
+ std::unique_ptr coreLib;
- retro_memory_map memoryMap{};
+ firelight::libretro::IRetropadProvider *m_retropadProvider;
+ std::shared_ptr audioReceiver;
+ std::shared_ptr m_configurationProvider;
- int audioVideoEnableBitmap;
+ retro_vfs_interface m_vfsInterface;
- retro_audio_callback *audioCallback;
- unsigned minimumAudioLatency;
- retro_midi_interface *midiInterface;
- retro_audio_buffer_status_callback *audioBufferStatusCallback;
+ vector environmentCalls;
- unsigned numActiveInputDevices;
- bool supportsInputBitmasks;
- vector controllerInfo;
- uint64_t inputDeviceCapabilitiesBitmask;
- retro_keyboard_callback *keyboardCallback;
+ retro_system_info *retroSystemInfo;
+ retro_system_av_info *retroSystemAVInfo;
- void recordPotentialAPIViolation(const string &msg);
+ // Informational to frontend.
+ bool canRunWithNoGame = false;
+ unsigned performanceLevel = 0;
+ bool supportsAchievements = false;
+ bool shutdown = false;
+ vector inputDescriptors;
- void *dll;
+ // Informational to core.
+ string systemDirectory;
+ string coreAssetsDirectory;
+ string saveDirectory;
+ string libretroPath;
+ string username;
+ unsigned frontendLanguage;
+ bool isJITCapable;
- void (*symRetroInit)();
+ retro_disk_control_callback *diskControlCallback;
+ unsigned diskControlInterfaceVersion;
+ retro_disk_control_ext_callback *diskControlExtCallback;
+ retro_rumble_interface *rumbleInterface;
+ uint64_t serializationQuirksBitmap;
+ retro_vfs_interface_info *virtualFileSystemInterfaceInfo;
+ retro_led_interface *ledInterface;
+ unsigned messageInterfaceVersion;
+ retro_message_ext *messageExt; // todo
+ retro_fastforwarding_override *fastforwardingOverride;
+ retro_system_content_info_override *contentInfoOverride;
+ retro_game_info_ext *gameInfoExt;
+ retro_throttle_state *throttleState;
+ int saveStateContext;
+ retro_microphone_interface *microphoneInterface;
+ retro_netpacket_callback *netpacketCallback;
+ retro_device_power *devicePower;
+ bool fastforwarding;
- void (*symRetroDeinit)();
+ retro_sensor_interface *sensorInterface;
+ retro_camera_callback *cameraCallback;
+ retro_log_callback *logCallback;
+ retro_perf_callback *performanceCallback;
+ retro_location_callback *locationCallback;
+ retro_get_proc_address_interface *procAddressCallback;
+ vector subsystemInfo;
+ vector memoryDescriptors;
- unsigned (*symRetroApiVersion)();
+ retro_memory_map memoryMap{};
- void (*symRetroGetSystemInfo)(retro_system_info *);
+ int audioVideoEnableBitmap;
- void (*symRetroGetSystemAVInfo)(retro_system_av_info *);
+ retro_audio_callback *audioCallback;
+ unsigned minimumAudioLatency;
+ retro_midi_interface *midiInterface;
+ retro_audio_buffer_status_callback *audioBufferStatusCallback;
- void (*symRetroSetControllerPortDevice)(unsigned, unsigned);
+ unsigned numActiveInputDevices;
+ bool supportsInputBitmasks;
+ vector controllerInfo;
+ uint64_t inputDeviceCapabilitiesBitmask;
+ retro_keyboard_callback *keyboardCallback;
- void (*symRetroReset)();
+ void recordPotentialAPIViolation(const string &msg);
- RetroRunFunc symRetroRun;
+ void *dll;
- size_t (*symRetroSerializeSize)();
+ void (*symRetroInit)();
- bool (*symRetroSerialize)(void *, size_t);
+ void (*symRetroDeinit)();
- bool (*symRetroUnserialize)(const void *, size_t);
+ unsigned (*symRetroApiVersion)();
- void (*symRetroCheatReset)();
+ void (*symRetroGetSystemInfo)(retro_system_info *);
- void (*symRetroCheatSet)(unsigned, bool, const char *);
+ void (*symRetroGetSystemAVInfo)(retro_system_av_info *);
- bool (*symRetroLoadGame)(const retro_game_info *);
+ void (*symRetroSetControllerPortDevice)(unsigned, unsigned);
- bool (*symRetroLoadGameSpecial)(unsigned, const retro_game_info *, size_t);
+ void (*symRetroReset)();
- void (*symRetroUnloadGame)();
+ RetroRunFunc symRetroRun;
- unsigned int (*symRetroGetRegion)();
+ size_t (*symRetroSerializeSize)();
- void *(*symRetroGetMemoryData)(unsigned);
+ bool (*symRetroSerialize)(void *, size_t);
- size_t (*symRetroGetMemoryDataSize)(unsigned);
-};
+ bool (*symRetroUnserialize)(const void *, size_t);
+ void (*symRetroCheatReset)();
+
+ void (*symRetroCheatSet)(unsigned, bool, const char *);
+
+ bool (*symRetroLoadGame)(const retro_game_info *);
+
+ bool (*symRetroLoadGameSpecial)(unsigned, const retro_game_info *, size_t);
+
+ void (*symRetroUnloadGame)();
+
+ unsigned int (*symRetroGetRegion)();
+
+ void *(*symRetroGetMemoryData)(unsigned);
+
+ size_t (*symRetroGetMemoryDataSize)(unsigned);
+ };
} // namespace libretro
diff --git a/src/app/libretro/core_configuration.cpp b/src/app/libretro/core_configuration.cpp
new file mode 100644
index 0000000..5531e5a
--- /dev/null
+++ b/src/app/libretro/core_configuration.cpp
@@ -0,0 +1,94 @@
+#include "core_configuration.hpp"
+
+#include
+
+static bool firstAccess = true;
+
+void CoreConfiguration::registerOption(Option option) {
+ m_options.emplace(option.key, option);
+
+ if (!m_defaultValues.contains(option.key)) {
+ setDefaultValue(option.key, option.defaultValueKey);
+ }
+}
+
+bool CoreConfiguration::anyOptionValueHasChanged() {
+ const auto val = m_changedSinceLastChecked;
+ m_changedSinceLastChecked = false;
+
+ return val;
+}
+
+void CoreConfiguration::setDefaultValue(const std::string key, const std::string value) {
+ const auto opt = m_options.find(key);
+ if (opt == m_options.end()) {
+ return;
+ }
+
+ for (auto &possibleValue: opt->second.possibleValues) {
+ if (possibleValue.key == value) {
+ m_defaultValues[key] = value;
+ m_changedSinceLastChecked = true;
+ break;
+ }
+ }
+}
+
+std::optional
+CoreConfiguration::getOptionValue(const std::string key) {
+ // if (firstAccess) {
+ // // print all keys and possible values
+ // for (const auto &option: m_options) {
+ // printf("Option %s (default: %s)\n", option.first.c_str(), option.second.defaultValueKey.c_str());
+ // //for (const auto &possibleValue: option.second.possibleValues) {
+ // // printf(" %s\n", possibleValue.key.c_str());
+ // //}
+ // }
+ //
+ //
+ // firstAccess = false;
+ // }
+
+ printf("Getting value for key %s...", key.c_str());
+
+ if (!m_options.contains(key)) {
+ return std::nullopt;
+ }
+
+ // First, check for values set by core
+ // Second, check for values set specifically for game
+ // Third, check for values set specifically for platformconst
+ //
+ auto value = m_gameValues.find(key);
+ if (value != m_gameValues.end()) {
+ printf("found game value %s\n", value->second.c_str());
+ return {{value->second, value->second}};
+ }
+
+ value = m_platformValues.find(key);
+ if (value != m_platformValues.end()) {
+ printf("found platform value %s\n", value->second.c_str());
+ return {{value->second, value->second}};
+ }
+
+ value = m_defaultValues.find(key);
+ if (value != m_defaultValues.end()) {
+ printf("found default value %s\n", value->second.c_str());
+ return {{value->second, value->second}};
+ }
+
+ return std::nullopt;
+}
+
+void CoreConfiguration::setOptionVisibility(std::string key, bool visible) {
+}
+
+void CoreConfiguration::setPlatformValue(const std::string &key, const std::string &value) {
+ m_platformValues[key] = value;
+ m_changedSinceLastChecked = true;
+}
+
+void CoreConfiguration::setGameValue(const std::string &key, const std::string &value) {
+ m_gameValues[key] = value;
+ m_changedSinceLastChecked = true;
+}
diff --git a/src/app/libretro/core_configuration.hpp b/src/app/libretro/core_configuration.hpp
new file mode 100644
index 0000000..6ce3db0
--- /dev/null
+++ b/src/app/libretro/core_configuration.hpp
@@ -0,0 +1,32 @@
+#pragma once
+#include