diff --git a/CMakeLists.txt b/CMakeLists.txt
index 553a0d6..7a4e939 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -19,8 +19,8 @@ qt_add_qml_module(appMinecraft_launcher
launcherbackend.cpp
QML_FILES
Main.qml
- RESOURCES images/Folder/Folder_Active.svg images/Folder/Folder_Idle.svg images/Folder/Folder_Pressed.svg images/Options/Options_active.svg images/Options/Options_Idle.svg images/Options/Options_Pressed.svg images/Play_Button/Play_Active.svg images/Play_Button/Play_Idle.svg images/Play_Button/Play_pressed.svg images/Profile_Box/Asset_23.svg images/Profile_Box/Asset_24.svg images/Profile_Box/Kishka_Profile_Active.svg images/Profile_Box/Kishka_Profile_Idle.svg images/Profile_Box/Kishka_Profile_open.svg images/Scroll/Scroll_Lever.svg images/Scroll/Scroll_palka.svg images/Version_box/Kishka_Profile_Open.svg images/Version_box/Kishka_Ver_Active.svg images/Version_box/Kishka_Ver_idle.svg images/Trash.svg images/GovuztTW8AAHqBf.jpeg images/photo_2025-12-16_15-04-17.jpg images/photo_2025-12-21_02-30-09.jpg
- RESOURCES images/Folder/Folder_Active.svg images/Folder/Folder_Idle.svg images/Folder/Folder_Pressed.svg images/Options/Options_active.svg images/Options/Options_Idle.svg images/Options/Options_Pressed.svg images/Play_Button/Play_Active.svg images/Play_Button/Play_Idle.svg images/Play_Button/Play_pressed.svg images/Profile_Box/Asset_23.jpg images/Profile_Box/Asset_23.svg images/Profile_Box/Asset_24.jpg images/Profile_Box/Asset_24.svg images/Profile_Box/Kishka_Profile_Active.svg images/Profile_Box/Kishka_Profile_Idle.svg images/Profile_Box/Kishka_Profile_open.svg images/Scroll/Scroll_Lever.svg images/Scroll/Scroll_palka.svg images/Version_box/Kishka_Profile_Open.svg images/Version_box/Kishka_Ver_Active.svg images/Version_box/Kishka_Ver_idle.svg images/Trash.svg images/GovuztTW8AAHqBf.jpeg images/photo_2025-12-16_15-04-17.jpg images/photo_2025-12-21_02-30-09.jpg
+ RESOURCES images/Folder/Folder_Active.svg images/Folder/Folder_Idle.svg images/Folder/Folder_Pressed.svg images/Options/Options_active.svg images/Options/Options_Idle.svg images/Options/Options_Pressed.svg images/Play_Button/Play_Active.svg images/Play_Button/Play_Idle.svg images/Play_Button/Play_pressed.svg images/Profile_Box/Asset_23.svg images/Profile_Box/Asset_24.svg images/Profile_Box/Kishka_Profile_Active.svg images/Profile_Box/Kishka_Profile_Idle.svg images/Profile_Box/Kishka_Profile_open.svg images/Scroll/Scroll_Lever.svg images/Scroll/Scroll_palka.svg images/Version_box/Kishka_Profile_Open.svg images/Version_box/Kishka_Ver_Active.svg images/Version_box/Kishka_Ver_idle.svg images/Trash.svg images/Pencil.svg images/GovuztTW8AAHqBf.jpeg images/photo_2025-12-16_15-04-17.jpg images/photo_2025-12-21_02-30-09.jpg
+ RESOURCES images/Folder/Folder_Active.svg images/Folder/Folder_Idle.svg images/Folder/Folder_Pressed.svg images/Options/Options_active.svg images/Options/Options_Idle.svg images/Options/Options_Pressed.svg images/Play_Button/Play_Active.svg images/Play_Button/Play_Idle.svg images/Play_Button/Play_pressed.svg images/Profile_Box/Asset_23.jpg images/Profile_Box/Asset_23.svg images/Profile_Box/Asset_24.jpg images/Profile_Box/Asset_24.svg images/Profile_Box/Kishka_Profile_Active.svg images/Profile_Box/Kishka_Profile_Idle.svg images/Profile_Box/Kishka_Profile_open.svg images/Scroll/Scroll_Lever.svg images/Scroll/Scroll_palka.svg images/Version_box/Kishka_Profile_Open.svg images/Version_box/Kishka_Ver_Active.svg images/Version_box/Kishka_Ver_idle.svg images/Trash.svg images/Pencil.svg images/GovuztTW8AAHqBf.jpeg images/photo_2025-12-16_15-04-17.jpg images/photo_2025-12-21_02-30-09.jpg
)
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
diff --git a/Main.qml b/Main.qml
index de6892a..1264e80 100644
--- a/Main.qml
+++ b/Main.qml
@@ -190,7 +190,7 @@ Window {
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
leftPadding: 8
- rightPadding: 36
+ rightPadding: 62
}
background: Rectangle {
color: (profileBox.currentIndex === profileItem.index || profileItem.hovered)
@@ -202,6 +202,28 @@ Window {
profileBox.popup.close()
}
+ // ── Карандаш — видна при наведении на строку ────
+ Image {
+ id: profilePencil
+ width: 16; height: 16
+ anchors.right: profileTrash.left
+ anchors.rightMargin: 12
+ anchors.verticalCenter: parent.verticalCenter
+ visible: profileItem.hovered
+ opacity: profilePencilArea.containsMouse ? 1.0 : 0.7
+ source: "images/Pencil.svg"
+ sourceSize.width: 16; sourceSize.height: 16
+ fillMode: Image.PreserveAspectFit
+
+ MouseArea {
+ id: profilePencilArea
+ anchors.fill: parent
+ anchors.margins: -6
+ hoverEnabled: true
+ onClicked: editProfileDialog.openFor(profileItem.index)
+ }
+ }
+
// ── Корзина — видна при наведении на строку ─────
Image {
id: profileTrash
@@ -354,7 +376,7 @@ Window {
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
leftPadding: 8
- rightPadding: 36
+ rightPadding: 62
}
background: Rectangle {
color: (versionBox.currentIndex === versionItem.index || versionItem.hovered)
@@ -366,6 +388,28 @@ Window {
versionBox.popup.close()
}
+ // ── Карандаш — видна при наведении на строку ────
+ Image {
+ id: versionPencil
+ width: 16; height: 16
+ anchors.right: versionTrash.left
+ anchors.rightMargin: 12
+ anchors.verticalCenter: parent.verticalCenter
+ visible: versionItem.hovered
+ opacity: versionPencilArea.containsMouse ? 1.0 : 0.7
+ source: "images/Pencil.svg"
+ sourceSize.width: 16; sourceSize.height: 16
+ fillMode: Image.PreserveAspectFit
+
+ MouseArea {
+ id: versionPencilArea
+ anchors.fill: parent
+ anchors.margins: -6
+ hoverEnabled: true
+ onClicked: editVersionDialog.openFor(versionItem.index)
+ }
+ }
+
// ── Корзина — видна при наведении на строку ─────
Image {
id: versionTrash
@@ -680,4 +724,303 @@ Window {
verName.text = ""; verServer.text = ""
}
}
+
+ // ── Edit Profile Dialog ────────────────────────────────────────────────
+ // Идентичен диалогу создания, но с предзаполненными данными выбранного
+ // профиля. По нажатию ОК данные перезаписываются под тем же индексом.
+ Dialog {
+ id: editProfileDialog
+ modal: true
+ width: 320
+ x: (window.width - width) / 2
+ y: (window.height - height) / 2
+ padding: 0
+
+ property int editIndex: -1
+
+ function openFor(index) {
+ const data = backend.profileAt(index)
+ editIndex = index
+ epName.text = data.name || ""
+ epLogin.text = data.login || ""
+ epPassword.text = data.password || ""
+ profileBox.popup.close()
+ open()
+ }
+
+ background: Rectangle {
+ color: "#1e1e1e"
+ radius: 10
+ border.color: "#91B315"
+ border.width: 1
+ }
+
+ header: Item {
+ implicitHeight: 52
+ Text {
+ anchors.centerIn: parent
+ text: "Редактирование профиля"
+ color: "#ffffff"
+ font.pixelSize: 17
+ font.bold: true
+ }
+ Rectangle {
+ anchors.bottom: parent.bottom
+ width: parent.width
+ height: 1
+ color: "#333333"
+ }
+ }
+
+ contentItem: Column {
+ spacing: 12
+ topPadding: 20
+ bottomPadding: 20
+
+ TextField {
+ id: epName
+ x: 20
+ width: parent.width - 40
+ placeholderText: "Имя профиля"
+ color: "#ffffff"
+ placeholderTextColor: "#666666"
+ background: Rectangle {
+ color: "#2a2a2a"
+ radius: 6
+ border.color: epName.activeFocus ? "#91B315" : "#444444"
+ border.width: 1
+ }
+ }
+
+ TextField {
+ id: epLogin
+ x: 20
+ width: parent.width - 40
+ placeholderText: "Логин"
+ color: "#ffffff"
+ placeholderTextColor: "#666666"
+ background: Rectangle {
+ color: "#2a2a2a"
+ radius: 6
+ border.color: epLogin.activeFocus ? "#91B315" : "#444444"
+ border.width: 1
+ }
+ }
+
+ TextField {
+ id: epPassword
+ x: 20
+ width: parent.width - 40
+ placeholderText: "Пароль"
+ echoMode: TextInput.Password
+ color: "#ffffff"
+ placeholderTextColor: "#666666"
+ background: Rectangle {
+ color: "#2a2a2a"
+ radius: 6
+ border.color: epPassword.activeFocus ? "#91B315" : "#444444"
+ border.width: 1
+ }
+ }
+ }
+
+ footer: Item {
+ implicitHeight: 60
+ Rectangle {
+ anchors.top: parent.top
+ width: parent.width
+ height: 1
+ color: "#333333"
+ }
+ Row {
+ anchors.centerIn: parent
+ spacing: 12
+
+ Button {
+ text: "Отмена"
+ width: 110; height: 36
+ contentItem: Text {
+ text: parent.text
+ color: "#ffffff"
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+ background: Rectangle {
+ color: parent.pressed ? "#444444" : "#333333"
+ radius: 6
+ }
+ onClicked: editProfileDialog.reject()
+ }
+
+ Button {
+ text: "ОК"
+ width: 110; height: 36
+ contentItem: Text {
+ text: parent.text
+ color: "#ffffff"
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+ background: Rectangle {
+ color: parent.pressed ? "#6a8510" : "#91B315"
+ radius: 6
+ }
+ onClicked: editProfileDialog.accept()
+ }
+ }
+ }
+
+ onAccepted: {
+ const name = epName.text.trim()
+ if (editIndex >= 0 && name !== "")
+ backend.updateProfile(editIndex, name, epLogin.text.trim(), epPassword.text)
+ editIndex = -1
+ epName.text = ""; epLogin.text = ""; epPassword.text = ""
+ }
+ onRejected: {
+ editIndex = -1
+ epName.text = ""; epLogin.text = ""; epPassword.text = ""
+ }
+ }
+
+ // ── Edit Version Dialog ────────────────────────────────────────────────
+ // Идентичен диалогу создания, но с предзаполненными данными выбранной
+ // версии. По нажатию ОК данные перезаписываются под тем же индексом.
+ Dialog {
+ id: editVersionDialog
+ modal: true
+ width: 320
+ x: (window.width - width) / 2
+ y: (window.height - height) / 2
+ padding: 0
+
+ property int editIndex: -1
+
+ function openFor(index) {
+ const data = backend.versionAt(index)
+ editIndex = index
+ evName.text = data.name || ""
+ evServer.text = data.serverUrl || ""
+ versionBox.popup.close()
+ open()
+ }
+
+ background: Rectangle {
+ color: "#1e1e1e"
+ radius: 10
+ border.color: "#91B315"
+ border.width: 1
+ }
+
+ header: Item {
+ implicitHeight: 52
+ Text {
+ anchors.centerIn: parent
+ text: "Редактирование версии"
+ color: "#ffffff"
+ font.pixelSize: 17
+ font.bold: true
+ }
+ Rectangle {
+ anchors.bottom: parent.bottom
+ width: parent.width
+ height: 1
+ color: "#333333"
+ }
+ }
+
+ contentItem: Column {
+ spacing: 12
+ topPadding: 20
+ bottomPadding: 20
+
+ TextField {
+ id: evName
+ x: 20
+ width: parent.width - 40
+ placeholderText: "Название версии"
+ color: "#ffffff"
+ placeholderTextColor: "#666666"
+ background: Rectangle {
+ color: "#2a2a2a"
+ radius: 6
+ border.color: evName.activeFocus ? "#91B315" : "#444444"
+ border.width: 1
+ }
+ }
+
+ TextField {
+ id: evServer
+ x: 20
+ width: parent.width - 40
+ placeholderText: "URL сервера загрузки"
+ color: "#ffffff"
+ placeholderTextColor: "#666666"
+ background: Rectangle {
+ color: "#2a2a2a"
+ radius: 6
+ border.color: evServer.activeFocus ? "#91B315" : "#444444"
+ border.width: 1
+ }
+ }
+ }
+
+ footer: Item {
+ implicitHeight: 60
+ Rectangle {
+ anchors.top: parent.top
+ width: parent.width
+ height: 1
+ color: "#333333"
+ }
+ Row {
+ anchors.centerIn: parent
+ spacing: 12
+
+ Button {
+ text: "Отмена"
+ width: 110; height: 36
+ contentItem: Text {
+ text: parent.text
+ color: "#ffffff"
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+ background: Rectangle {
+ color: parent.pressed ? "#444444" : "#333333"
+ radius: 6
+ }
+ onClicked: editVersionDialog.reject()
+ }
+
+ Button {
+ text: "ОК"
+ width: 110; height: 36
+ contentItem: Text {
+ text: parent.text
+ color: "#ffffff"
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+ background: Rectangle {
+ color: parent.pressed ? "#6a8510" : "#91B315"
+ radius: 6
+ }
+ onClicked: editVersionDialog.accept()
+ }
+ }
+ }
+
+ onAccepted: {
+ const name = evName.text.trim()
+ if (editIndex >= 0 && name !== "")
+ backend.updateVersion(editIndex, name, evServer.text.trim())
+ editIndex = -1
+ evName.text = ""; evServer.text = ""
+ }
+ onRejected: {
+ editIndex = -1
+ evName.text = ""; evServer.text = ""
+ }
+ }
}
diff --git a/images/Pencil.svg b/images/Pencil.svg
new file mode 100644
index 0000000..c74fff9
--- /dev/null
+++ b/images/Pencil.svg
@@ -0,0 +1,4 @@
+
diff --git a/launcherbackend.cpp b/launcherbackend.cpp
index b8b61c5..6203eb3 100644
--- a/launcherbackend.cpp
+++ b/launcherbackend.cpp
@@ -46,6 +46,43 @@ void LauncherBackend::addVersion(const QString &name, const QString &serverUrl)
emit versionsChanged();
}
+void LauncherBackend::updateProfile(int index, const QString &name, const QString &login, const QString &password)
+{
+ if (index < 0 || index >= m_profiles.size())
+ return;
+ m_profiles[index].name = name;
+ m_profiles[index].login = login;
+ m_profiles[index].password = password;
+ saveProfiles();
+ emit profilesChanged();
+}
+
+void LauncherBackend::updateVersion(int index, const QString &name, const QString &serverUrl)
+{
+ if (index < 0 || index >= m_versions.size())
+ return;
+ m_versions[index].name = name;
+ m_versions[index].serverUrl = serverUrl;
+ saveVersions();
+ emit versionsChanged();
+}
+
+QVariantMap LauncherBackend::profileAt(int index) const
+{
+ if (index < 0 || index >= m_profiles.size())
+ return {};
+ const auto &p = m_profiles[index];
+ return {{"name", p.name}, {"login", p.login}, {"password", p.password}};
+}
+
+QVariantMap LauncherBackend::versionAt(int index) const
+{
+ if (index < 0 || index >= m_versions.size())
+ return {};
+ const auto &v = m_versions[index];
+ return {{"name", v.name}, {"serverUrl", v.serverUrl}};
+}
+
void LauncherBackend::removeProfile(int index)
{
if (index < 0 || index >= m_profiles.size())
diff --git a/launcherbackend.h b/launcherbackend.h
index 5035c8f..34bbec4 100644
--- a/launcherbackend.h
+++ b/launcherbackend.h
@@ -2,6 +2,7 @@
#include
#include
+#include
#include
class LauncherBackend : public QObject
@@ -20,6 +21,10 @@ public:
Q_INVOKABLE void addProfile(const QString &name, const QString &login, const QString &password);
Q_INVOKABLE void addVersion(const QString &name, const QString &serverUrl);
+ Q_INVOKABLE void updateProfile(int index, const QString &name, const QString &login, const QString &password);
+ Q_INVOKABLE void updateVersion(int index, const QString &name, const QString &serverUrl);
+ Q_INVOKABLE QVariantMap profileAt(int index) const;
+ Q_INVOKABLE QVariantMap versionAt(int index) const;
Q_INVOKABLE void removeProfile(int index);
Q_INVOKABLE void removeVersion(int index);
Q_INVOKABLE void launchGame(int profileIndex, int versionIndex);