diff --git a/bin/GuiConfigs/Classic (Bright).qss b/bin/GuiConfigs/Classic (Bright).qss index 130a8ab4f6..72c7ea7041 100644 --- a/bin/GuiConfigs/Classic (Bright).qss +++ b/bin/GuiConfigs/Classic (Bright).qss @@ -43,20 +43,6 @@ QLabel#gamelist_icon_background_color { color: rgba(209,209,209,255); } -/* game grid stylesheet */ -QTableWidget#game_grid { - font-weight:600; - font-size:8pt; - font-family:Lucida Grande; - color: rgba(51,51,51,255); -} -QTableWidget#game_grid::item:selected:active { - selection-background-color: #0078D7; -} -QTableWidget#game_grid::item:selected:!active { - selection-background-color: #008cff; -} - /* log stylesheet */ QTextEdit#tty_frame { background-color:#ffffff; @@ -131,3 +117,33 @@ QLabel#rsx_debugger_display_buffer { QLabel#l_controller { color:#434343; } + +/* Game Grid */ +#game_list_grid_item[selected="true"] { + background: #0078D7; +} +#game_list_grid_item:hover { + background: #008cff; +} +#game_list_grid_item:focus { + background: #0078D7; +} + +/* Game Grid Font */ +#game_list_grid_item #game_list_grid_item_title_label { + font-weight: 600; + font-size: 8pt; + font-family: Lucida Grande; + color: rgba(51,51,51,255); +} + +/* Game Grid hover and focus: we need to handle properties differently when using descendants */ +#game_list_grid_item[selected="true"] #game_list_grid_item_title_label { + color: #fff; +} +#game_list_grid_item[hover="true"] #game_list_grid_item_title_label { + color: #fff; +} +#game_list_grid_item[focus="true"] #game_list_grid_item_title_label { + color: #fff; +} diff --git a/bin/GuiConfigs/Darker Style by TheMitoSan.qss b/bin/GuiConfigs/Darker Style by TheMitoSan.qss index f46cc2e0b1..c4d867cd1b 100644 --- a/bin/GuiConfigs/Darker Style by TheMitoSan.qss +++ b/bin/GuiConfigs/Darker Style by TheMitoSan.qss @@ -197,17 +197,6 @@ QLabel#l_controller { color: #FFF; } -/* Game Grid Font */ -QTableWidget#game_grid { - font-weight: 600; - font-size: 8pt; - font-family: Lucida Grande; - color: #FFF; -} -QTableWidget#game_grid::item:selected:!active { - selection-background-color: #313f4e; -} - /* Slider on toolbar */ QWidget#sizeSliderContainer { background: transparent; @@ -344,24 +333,54 @@ QLabel#debugger_frame_pc { /* Tree view changes*/ QTreeView::branch:has-children:!has-siblings:closed, QTreeView::branch:closed:has-children:has-siblings { - border-image: none; - image: url("GuiConfigs/list_arrow_white.png"); + border-image: none; + image: url("GuiConfigs/list_arrow_white.png"); } QTreeView::branch:open:has-children:!has-siblings, -QTreeView::branch:open:has-children:has-siblings { - border-image: none; - image: url("GuiConfigs/list_arrow_down_white.png"); +QTreeView::branch:open:has-children:has-siblings { + border-image: none; + image: url("GuiConfigs/list_arrow_down_white.png"); } QTreeView::branch:has-children:!has-siblings:closed:hover, QTreeView::branch:closed:has-children:has-siblings:hover { - border-image: none; - image: url("GuiConfigs/list_arrow_blue.png"); + border-image: none; + image: url("GuiConfigs/list_arrow_blue.png"); } QTreeView::branch:open:has-children:!has-siblings:hover, -QTreeView::branch:open:has-children:has-siblings:hover { - border-image: none; - image: url("GuiConfigs/list_arrow_down_blue.png"); +QTreeView::branch:open:has-children:has-siblings:hover { + border-image: none; + image: url("GuiConfigs/list_arrow_down_blue.png"); +} + +/* Game Grid */ +#game_list_grid_item[selected="true"] { + background: #313f4e; +} +#game_list_grid_item:hover { + background: #313f4e; +} +#game_list_grid_item:focus { + background: #0078D7; +} + +/* Game Grid Font */ +#game_list_grid_item #game_list_grid_item_title_label { + font-weight: 600; + font-size: 8pt; + font-family: Lucida Grande; + color: #FFF; +} + +/* Game Grid hover and focus: we need to handle properties differently when using descendants */ +#game_list_grid_item[selected="true"] #game_list_grid_item_title_label { + background-color: #313f4e; +} +#game_list_grid_item[hover="true"] #game_list_grid_item_title_label { + background-color: #313f4e; +} +#game_list_grid_item[focus="true"] #game_list_grid_item_title_label { + background-color: #0078D7; } diff --git a/bin/GuiConfigs/Envy.qss b/bin/GuiConfigs/Envy.qss index 956ec34b1f..8bc80166c1 100644 --- a/bin/GuiConfigs/Envy.qss +++ b/bin/GuiConfigs/Envy.qss @@ -454,7 +454,7 @@ QListWidget::item:selected { } QListWidget::item:hover { - background-color: #30333a; + background-color: #30333a; color: #8cf944; border-radius: 0.25em; } @@ -565,7 +565,7 @@ QTableView { } QTableView::item:hover { - color: #8cf944; + color: #8cf944; } /* Game Icon Background */ @@ -641,24 +641,42 @@ QLabel#debugger_frame_pc { /* Tree view changes*/ QTreeView::branch:has-children:!has-siblings:closed, QTreeView::branch:closed:has-children:has-siblings { - border-image: none; - image: url("GuiConfigs/list_arrow_white.png"); + border-image: none; + image: url("GuiConfigs/list_arrow_white.png"); } QTreeView::branch:open:has-children:!has-siblings, -QTreeView::branch:open:has-children:has-siblings { - border-image: none; - image: url("GuiConfigs/list_arrow_down_white.png"); +QTreeView::branch:open:has-children:has-siblings { + border-image: none; + image: url("GuiConfigs/list_arrow_down_white.png"); } QTreeView::branch:has-children:!has-siblings:closed:hover, QTreeView::branch:closed:has-children:has-siblings:hover { - border-image: none; - image: url("GuiConfigs/list_arrow_green.png"); + border-image: none; + image: url("GuiConfigs/list_arrow_green.png"); } QTreeView::branch:open:has-children:!has-siblings:hover, -QTreeView::branch:open:has-children:has-siblings:hover { - border-image: none; - image: url("GuiConfigs/list_arrow_down_green.png"); +QTreeView::branch:open:has-children:has-siblings:hover { + border-image: none; + image: url("GuiConfigs/list_arrow_down_green.png"); +} + +/* Game Grid */ +#game_list_grid_item[selected="true"] { + background: #2d3038; +} +#game_list_grid_item:focus { + background: #2d3038; +} + +/* Game Grid hover and focus: we need to handle properties differently when using descendants */ +#game_list_grid_item[selected="true"] #game_list_grid_item_title_label { + background-color: #2d3038; + color: #8cf944; +} +#game_list_grid_item[focus="true"] #game_list_grid_item_title_label { + background-color: #2d3038; + color: #8cf944; } diff --git a/bin/GuiConfigs/Kuroi (Dark) by Ani.qss b/bin/GuiConfigs/Kuroi (Dark) by Ani.qss index 0f63d148ed..ba46bb5831 100644 --- a/bin/GuiConfigs/Kuroi (Dark) by Ani.qss +++ b/bin/GuiConfigs/Kuroi (Dark) by Ani.qss @@ -249,20 +249,6 @@ QWidget#trophy_notification_frame { color: #e6e6e6; } -/* Game Grid Font */ -QTableWidget#game_grid { - font-weight: 600; - font-size: 8pt; - font-family: Lucida Grande; - selection-color: #e6e6e6; -} -QTableWidget#game_grid::item:selected:active { - selection-background-color: #4c4b4b; -} -QTableWidget#game_grid::item:selected:!active { - selection-background-color: #3d3d3d; -} - /* Set Toolbar Slider Size */ QSlider#sizeSlider::groove:horizontal { border: 0em solid transparent; @@ -347,24 +333,54 @@ QLabel#tty_text { /* Tree view changes*/ QTreeView::branch:has-children:!has-siblings:closed, QTreeView::branch:closed:has-children:has-siblings { - border-image: none; - image: url("GuiConfigs/list_arrow_white.png"); + border-image: none; + image: url("GuiConfigs/list_arrow_white.png"); } QTreeView::branch:open:has-children:!has-siblings, -QTreeView::branch:open:has-children:has-siblings { - border-image: none; - image: url("GuiConfigs/list_arrow_down_white.png"); +QTreeView::branch:open:has-children:has-siblings { + border-image: none; + image: url("GuiConfigs/list_arrow_down_white.png"); } QTreeView::branch:has-children:!has-siblings:closed:hover, QTreeView::branch:closed:has-children:has-siblings:hover { - border-image: none; - image: url("GuiConfigs/list_arrow_blue.png"); + border-image: none; + image: url("GuiConfigs/list_arrow_blue.png"); } QTreeView::branch:open:has-children:!has-siblings:hover, -QTreeView::branch:open:has-children:has-siblings:hover { - border-image: none; - image: url("GuiConfigs/list_arrow_down_blue.png"); +QTreeView::branch:open:has-children:has-siblings:hover { + border-image: none; + image: url("GuiConfigs/list_arrow_down_blue.png"); +} + +/* Game Grid */ +#game_list_grid_item[selected="true"] { + background: #3d3d3d; +} +#game_list_grid_item:hover { + background: #3d3d3d; +} +#game_list_grid_item:focus { + background: #4c4b4b; +} + +/* Game Grid Font */ +#game_list_grid_item #game_list_grid_item_title_label { + font-weight: 600; + font-size: 8pt; + font-family: Lucida Grande; +} + +/* Game Grid hover and focus: we need to handle properties differently when using descendants */ +#game_list_grid_item[selected="true"] #game_list_grid_item_title_label { + background-color: #3d3d3d; +} +#game_list_grid_item[hover="true"] #game_list_grid_item_title_label { + background-color: #3d3d3d; +} +#game_list_grid_item[focus="true"] #game_list_grid_item_title_label { + color: #e6e6e6; + background-color: #4c4b4b; } diff --git a/bin/GuiConfigs/ModernBlue Theme by TheMitoSan.qss b/bin/GuiConfigs/ModernBlue Theme by TheMitoSan.qss index 2c48ba05c9..44f7902353 100644 --- a/bin/GuiConfigs/ModernBlue Theme by TheMitoSan.qss +++ b/bin/GuiConfigs/ModernBlue Theme by TheMitoSan.qss @@ -194,17 +194,6 @@ QLabel#l_controller { color: #fff; } -/* Game Grid Font */ -QTableWidget#game_grid { - font-weight: 600; - font-size: 8pt; - font-family: Lucida Grande; - color: #fff; -} -QTableWidget#game_grid::item:selected:!active { - selection-background-color: #354454; -} - /* Slider on toolbar */ QWidget#sizeSliderContainer { background: transparent; @@ -341,24 +330,54 @@ QLabel#debugger_frame_pc { /* Tree view changes*/ QTreeView::branch:has-children:!has-siblings:closed, QTreeView::branch:closed:has-children:has-siblings { - border-image: none; - image: url("GuiConfigs/list_arrow_white.png"); + border-image: none; + image: url("GuiConfigs/list_arrow_white.png"); } QTreeView::branch:open:has-children:!has-siblings, -QTreeView::branch:open:has-children:has-siblings { - border-image: none; - image: url("GuiConfigs/list_arrow_down_white.png"); +QTreeView::branch:open:has-children:has-siblings { + border-image: none; + image: url("GuiConfigs/list_arrow_down_white.png"); } QTreeView::branch:has-children:!has-siblings:closed:hover, QTreeView::branch:closed:has-children:has-siblings:hover { - border-image: none; - image: url("GuiConfigs/list_arrow_blue.png"); + border-image: none; + image: url("GuiConfigs/list_arrow_blue.png"); } QTreeView::branch:open:has-children:!has-siblings:hover, -QTreeView::branch:open:has-children:has-siblings:hover { - border-image: none; - image: url("GuiConfigs/list_arrow_down_blue.png"); +QTreeView::branch:open:has-children:has-siblings:hover { + border-image: none; + image: url("GuiConfigs/list_arrow_down_blue.png"); +} + +/* Game Grid */ +#game_list_grid_item[selected="true"] { + background: #354454; +} +#game_list_grid_item:hover { + background: #354454; +} +#game_list_grid_item:focus { + background: #0078D7; +} + +/* Game Grid Font */ +#game_list_grid_item #game_list_grid_item_title_label { + font-weight: 600; + font-size: 8pt; + font-family: Lucida Grande; + color: #FFF; +} + +/* Game Grid hover and focus: we need to handle properties differently when using descendants */ +#game_list_grid_item[selected="true"] #game_list_grid_item_title_label { + background-color: #354454; +} +#game_list_grid_item[hover="true"] #game_list_grid_item_title_label { + background-color: #354454; +} +#game_list_grid_item[focus="true"] #game_list_grid_item_title_label { + background-color: #0078D7; } diff --git a/bin/GuiConfigs/Nekotekina by GooseWing.qss b/bin/GuiConfigs/Nekotekina by GooseWing.qss index 24cb7064df..eba076ff55 100755 --- a/bin/GuiConfigs/Nekotekina by GooseWing.qss +++ b/bin/GuiConfigs/Nekotekina by GooseWing.qss @@ -367,21 +367,6 @@ QLabel#l_controller { } -/* Game Grid Font */ -QTableWidget#game_grid { - font-weight: 600; - color: #ffd785; - text-transform: uppercase; - selection-color: #ffd785; -} -QTableWidget#game_grid::item:selected:active { - selection-background-color: #4d4940; -} -QTableWidget#game_grid::item:selected:!active { - selection-background-color: #615c51; -} - - /* Debug UI Settings buttons */ QLabel#color_button { background: transparent; @@ -489,3 +474,33 @@ QWidget#trophy_notification_frame { background-color: #b3ac98; color: #4d4940; } + +/* Game Grid */ +#game_list_grid_item[selected="true"] { + background: #615c51; +} +#game_list_grid_item:hover { + background: #615c51; +} +#game_list_grid_item:focus { + background: #4d4940; +} + +/* Game Grid Font */ +#game_list_grid_item #game_list_grid_item_title_label { + font-weight: 600; + text-transform: uppercase; + color: #ffd785; +} + +/* Game Grid hover and focus: we need to handle properties differently when using descendants */ +#game_list_grid_item[selected="true"] #game_list_grid_item_title_label { + background-color: #615c51; +} +#game_list_grid_item[hover="true"] #game_list_grid_item_title_label { + background-color: #615c51; +} +#game_list_grid_item[focus="true"] #game_list_grid_item_title_label { + background-color: #4d4940; + color: #ffd785; +} diff --git a/bin/GuiConfigs/Skyline (Nightfall).qss b/bin/GuiConfigs/Skyline (Nightfall).qss index 5af056fdd3..5271643ac3 100644 --- a/bin/GuiConfigs/Skyline (Nightfall).qss +++ b/bin/GuiConfigs/Skyline (Nightfall).qss @@ -220,6 +220,17 @@ QScrollBar::up-arrow, QScrollBar::down-arrow, QScrollBar::up-button:vertical, QS border: none; } +#game_list_grid QScrollBar { + width: 10px; + height: 3px; +} +#game_list_grid QScrollBar::handle { + background: #1c273d; +} +#game_list_grid QScrollBar::handle:hover, QScrollBar::handle:pressed { + background: #0074e7; +} + /* Radio Buttons */ QRadioButton::indicator { border: 0.0625em solid #455971; @@ -663,24 +674,49 @@ QLabel#debugger_frame_pc { /* Tree view changes*/ QTreeView::branch:has-children:!has-siblings:closed, QTreeView::branch:closed:has-children:has-siblings { - border-image: none; - image: url("GuiConfigs/list_arrow_white.png"); + border-image: none; + image: url("GuiConfigs/list_arrow_white.png"); } QTreeView::branch:open:has-children:!has-siblings, -QTreeView::branch:open:has-children:has-siblings { - border-image: none; - image: url("GuiConfigs/list_arrow_down_white.png"); +QTreeView::branch:open:has-children:has-siblings { + border-image: none; + image: url("GuiConfigs/list_arrow_down_white.png"); } QTreeView::branch:has-children:!has-siblings:closed:hover, QTreeView::branch:closed:has-children:has-siblings:hover { - border-image: none; - image: url("GuiConfigs/list_arrow_blue.png"); + border-image: none; + image: url("GuiConfigs/list_arrow_blue.png"); } QTreeView::branch:open:has-children:!has-siblings:hover, -QTreeView::branch:open:has-children:has-siblings:hover { - border-image: none; - image: url("GuiConfigs/list_arrow_down_blue.png"); +QTreeView::branch:open:has-children:has-siblings:hover { + border-image: none; + image: url("GuiConfigs/list_arrow_down_blue.png"); +} + +/* Game Grid */ +#game_list_grid_item[selected="true"] { + background: #0f1d36; +} +#game_list_grid_item:focus { + background: #0f1d36; +} + +#game_list_grid_item #game_list_grid_item_title_label { + color: #FFFFFF; +} + +/* Game Grid hover and focus: we need to handle properties differently when using descendants */ +#game_list_grid_item[selected="true"] #game_list_grid_item_title_label { + background-color: #0f1d36; + color: #2397ff; +} +#game_list_grid_item[hover="true"] #game_list_grid_item_title_label { + color: #2397ff; +} +#game_list_grid_item[focus="true"] #game_list_grid_item_title_label { + background-color: #0f1d36; + color: #2397ff; } diff --git a/bin/GuiConfigs/Skyline.qss b/bin/GuiConfigs/Skyline.qss index e2de14a9f7..c1339e96ea 100644 --- a/bin/GuiConfigs/Skyline.qss +++ b/bin/GuiConfigs/Skyline.qss @@ -678,3 +678,24 @@ QLabel#debugger_frame_pc { color: #000; /* Font Color: Black */ background-color: #00ff00; /* Green */ } + +/* Game Grid */ +#game_list_grid_item[selected="true"] { + background: #4343c1; +} +#game_list_grid_item:focus { + background: #4343c1; +} + +/* Game Grid hover and focus: we need to handle properties differently when using descendants */ +#game_list_grid_item[selected="true"] #game_list_grid_item_title_label { + background-color: #4343c1; + color: #FFF; +} +#game_list_grid_item[hover="true"] #game_list_grid_item_title_label { + color: #4343c1; +} +#game_list_grid_item[focus="true"] #game_list_grid_item_title_label { + background-color: #4343c1; + color: #FFF; +} diff --git a/bin/GuiConfigs/YoRHa by Ani.qss b/bin/GuiConfigs/YoRHa by Ani.qss index 527f9f25d0..c3345d4cd9 100644 --- a/bin/GuiConfigs/YoRHa by Ani.qss +++ b/bin/GuiConfigs/YoRHa by Ani.qss @@ -358,14 +358,6 @@ QLabel#l_controller { color: #4d4940; } -/* Game Grid Font */ -QTableWidget#game_grid { - font-weight: 600; - color: #4d4940; - text-transform: uppercase; - selection-color: #aea993; -} - /* Debug UI Settings buttons */ QLabel#color_button { background: transparent; @@ -463,3 +455,26 @@ QSlider#sizeSlider::groove:horizontal { QLabel#validLabel { font-weight: 600; } + +/* Game Grid */ +#game_list_grid_item[selected="true"] { + background: #4d4940; +} +#game_list_grid_item:focus { + background: #4d4940; +} + +/* Game Grid Font */ +#game_list_grid_item #game_list_grid_item_title_label { + font-weight: 600; + color: #4d4940; + text-transform: uppercase; +} + +/* Game Grid hover and focus: we need to handle properties differently when using descendants */ +#game_list_grid_item[selected="true"] #game_list_grid_item_title_label { + color: #aea993; +} +#game_list_grid_item[focus="true"] #game_list_grid_item_title_label { + color: #aea993; +} diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index 817038040a..eb20fe5be9 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -234,6 +234,12 @@ true + + true + + + true + true @@ -246,6 +252,12 @@ true + + true + + + true + true @@ -348,6 +360,9 @@ true + + true + true @@ -471,6 +486,12 @@ true + + true + + + true + true @@ -483,6 +504,12 @@ true + + true + + + true + true @@ -585,6 +612,9 @@ true + + true + true @@ -667,8 +697,12 @@ + + + + @@ -676,6 +710,7 @@ + @@ -691,6 +726,7 @@ + @@ -713,7 +749,6 @@ - @@ -1148,6 +1183,16 @@ + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DMINIUPNP_STATICLIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DMINIUPNP_STATICLIB -DHAVE_SDL2 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\libsdl-org\SDL\include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + $(QTDIR)\bin\moc.exe;%(FullPath) + $(QTDIR)\bin\moc.exe;%(FullPath) + + @@ -1161,7 +1206,37 @@ + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DMINIUPNP_STATICLIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DMINIUPNP_STATICLIB -DHAVE_SDL2 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\libsdl-org\SDL\include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DMINIUPNP_STATICLIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DMINIUPNP_STATICLIB -DHAVE_SDL2 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\libsdl-org\SDL\include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DMINIUPNP_STATICLIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DMINIUPNP_STATICLIB -DHAVE_SDL2 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\libsdl-org\SDL\include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + $(QTDIR)\bin\moc.exe;%(FullPath) @@ -1214,6 +1289,7 @@ "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + Moc%27ing %(Identity)... @@ -1310,6 +1386,16 @@ .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DMINIUPNP_STATICLIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DMINIUPNP_STATICLIB -DHAVE_SDL2 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\libsdl-org\SDL\include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + @@ -1416,7 +1502,6 @@ .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DMINIUPNP_STATICLIB -DHAVE_SDL2 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\libsdl-org\SDL\include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" - @@ -1752,6 +1837,17 @@ "$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)" + + + + + + + + + + + diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index e961d160d2..5369594056 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -154,6 +154,9 @@ {9191ae51-8b80-4606-b5bc-966a9588f538} + + {b927140a-5214-4628-a648-8380ae793179} + @@ -381,9 +384,6 @@ Gui\game list - - Gui\game list - Gui\dev tools @@ -954,6 +954,54 @@ Gui\flow_layout + + Gui\game list + + + Generated Files\Debug + + + Generated Files\Release + + + Gui\game list + + + Gui\custom items + + + Gui\game list + + + Generated Files\Debug + + + Generated Files\Release + + + Gui\flow_layout + + + Generated Files\Debug + + + Generated Files\Release + + + Gui\screenshot manager + + + Generated Files\Debug + + + Generated Files\Release + + + Generated Files\Debug + + + Generated Files\Release + @@ -992,9 +1040,6 @@ Gui\game window - - Gui\game list - Io\evdev @@ -1130,6 +1175,9 @@ Gui\flow_layout + + Gui\custom items + @@ -1408,6 +1456,21 @@ Gui\flow_layout + + Gui\game list + + + Gui\game list + + + Gui\game list + + + Gui\screenshot manager + + + Gui\flow_layout + @@ -1415,4 +1478,33 @@ + + + StyleSheets + + + StyleSheets + + + StyleSheets + + + StyleSheets + + + StyleSheets + + + StyleSheets + + + StyleSheets + + + StyleSheets + + + StyleSheets + + \ No newline at end of file diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt index 6262889249..fab0863724 100644 --- a/rpcs3/rpcs3qt/CMakeLists.txt +++ b/rpcs3/rpcs3qt/CMakeLists.txt @@ -24,12 +24,15 @@ set(SRC_FILES find_dialog.cpp flow_layout.cpp flow_widget.cpp + flow_widget_item.cpp game_compatibility.cpp game_list.cpp + game_list_base.cpp game_list_delegate.cpp game_list_frame.cpp game_list_grid.cpp - game_list_grid_delegate.cpp + game_list_grid_item.cpp + game_list_table.cpp gui_application.cpp gl_gs_frame.cpp gs_frame.cpp @@ -47,6 +50,7 @@ set(SRC_FILES memory_viewer_panel.cpp microphone_creator.cpp movie_item.cpp + movie_item_base.cpp msg_dialog_frame.cpp osk_dialog_frame.cpp pad_led_settings_dialog.cpp @@ -73,6 +77,7 @@ set(SRC_FILES save_data_info_dialog.cpp save_data_list_dialog.cpp save_manager_dialog.cpp + screenshot_item.cpp screenshot_manager_dialog.cpp screenshot_preview.cpp sendmessage_dialog_frame.cpp diff --git a/rpcs3/rpcs3qt/flow_layout.cpp b/rpcs3/rpcs3qt/flow_layout.cpp index 698999dbed..730f0c6a18 100644 --- a/rpcs3/rpcs3qt/flow_layout.cpp +++ b/rpcs3/rpcs3qt/flow_layout.cpp @@ -51,14 +51,23 @@ #include #include "flow_layout.h" -flow_layout::flow_layout(QWidget* parent, int margin, int hSpacing, int vSpacing) - : QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing) +flow_layout::flow_layout(QWidget* parent, int margin, bool dynamic_spacing, int hSpacing, int vSpacing) + : QLayout(parent) + , m_dynamic_spacing(dynamic_spacing) + , m_hSpaceInitial(hSpacing) + , m_vSpaceInitial(vSpacing) + , m_hSpace(hSpacing) + , m_vSpace(vSpacing) { setContentsMargins(margin, margin, margin, margin); } -flow_layout::flow_layout(int margin, int hSpacing, int vSpacing) - : m_hSpace(hSpacing), m_vSpace(vSpacing) +flow_layout::flow_layout(int margin, bool dynamic_spacing, int hSpacing, int vSpacing) + : m_dynamic_spacing(dynamic_spacing) + , m_hSpaceInitial(hSpacing) + , m_vSpaceInitial(vSpacing) + , m_hSpace(hSpacing) + , m_vSpace(vSpacing) { setContentsMargins(margin, margin, margin, margin); } @@ -76,10 +85,12 @@ void flow_layout::clear() delete item; } itemList.clear(); + m_positions.clear(); } void flow_layout::addItem(QLayoutItem* item) { + m_positions.append(position{ .row = -1, .col = -1 }); itemList.append(item); } @@ -116,7 +127,10 @@ QLayoutItem* flow_layout::itemAt(int index) const QLayoutItem* flow_layout::takeAt(int index) { if (index >= 0 && index < itemList.size()) + { + m_positions.takeAt(index); return itemList.takeAt(index); + } return nullptr; } @@ -167,13 +181,57 @@ int flow_layout::doLayout(const QRect& rect, bool testOnly) const { int left, top, right, bottom; getContentsMargins(&left, &top, &right, &bottom); - QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom); + const QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom); int x = effectiveRect.x(); int y = effectiveRect.y(); int lineHeight = 0; + int rows = 0; + int cols = 0; - for (QLayoutItem* item : qAsConst(itemList)) + if (m_dynamic_spacing) { + const int available_width = effectiveRect.width(); + const int min_spacing = smartSpacing(QStyle::PM_LayoutHorizontalSpacing); + bool fits_into_width = true; + int width = 0; + int index = 0; + + for (; index < itemList.size(); index++) + { + if (QLayoutItem* item = itemList.at(index)) + { + const int new_width = width + item->sizeHint().width() + (width > 0 ? min_spacing : 0); + + if (new_width > effectiveRect.width()) + { + fits_into_width = false; + break; + } + + width = new_width; + } + } + + // Try to evenly distribute the items across the width + m_hSpace = (index == 0) ? -1 : ((available_width - width) / index); + + if (fits_into_width) + { + // Make sure there aren't huge gaps between the items + m_hSpace = smartSpacing(QStyle::PM_LayoutHorizontalSpacing); + } + } + else + { + m_hSpace = m_hSpaceInitial; + } + + for (int i = 0, row = 0, col = 0; i < itemList.size(); i++) + { + QLayoutItem* item = itemList.at(i); + if (!item) + continue; + const QWidget* wid = item->widget(); if (!wid) continue; @@ -193,20 +251,33 @@ int flow_layout::doLayout(const QRect& rect, bool testOnly) const y = y + lineHeight + spaceY; nextX = x + item->sizeHint().width() + spaceX; lineHeight = 0; + col = 0; + row++; } + position& pos = m_positions[i]; + pos.row = row; + pos.col = col++; + + rows = std::max(rows, pos.row + 1); + cols = std::max(cols, pos.col + 1); + if (!testOnly) item->setGeometry(QRect(QPoint(x, y), item->sizeHint())); x = nextX; lineHeight = qMax(lineHeight, item->sizeHint().height()); } + + m_rows = rows; + m_cols = cols; + return y + lineHeight - rect.y() + bottom; } int flow_layout::smartSpacing(QStyle::PixelMetric pm) const { - QObject* parent = this->parent(); + const QObject* parent = this->parent(); if (!parent) { return -1; @@ -214,9 +285,9 @@ int flow_layout::smartSpacing(QStyle::PixelMetric pm) const if (parent->isWidgetType()) { - QWidget* pw = static_cast(parent); + const QWidget* pw = static_cast(parent); return pw->style()->pixelMetric(pm, nullptr, pw); } - return static_cast(parent)->spacing(); + return static_cast(parent)->spacing(); } diff --git a/rpcs3/rpcs3qt/flow_layout.h b/rpcs3/rpcs3qt/flow_layout.h index b395b2cbcc..e8a4df65bd 100644 --- a/rpcs3/rpcs3qt/flow_layout.h +++ b/rpcs3/rpcs3qt/flow_layout.h @@ -57,15 +57,23 @@ class flow_layout : public QLayout { public: - explicit flow_layout(QWidget* parent, int margin = -1, int hSpacing = -1, int vSpacing = -1); - explicit flow_layout(int margin = -1, int hSpacing = -1, int vSpacing = -1); + struct position + { + int row{}; + int col{}; + }; + + explicit flow_layout(QWidget* parent, int margin = -1, bool dynamic_spacing = false, int hSpacing = -1, int vSpacing = -1); + explicit flow_layout(int margin = -1, bool dynamic_spacing = false, int hSpacing = -1, int vSpacing = -1); ~flow_layout(); void clear(); + const QList& item_list() const { return itemList; } + const QList& positions() const { return m_positions; } + int rows() const { return m_rows; } + int cols() const { return m_cols; } void addItem(QLayoutItem* item) override; - int horizontalSpacing() const; - int verticalSpacing() const; Qt::Orientations expandingDirections() const override; bool hasHeightForWidth() const override; int heightForWidth(int) const override; @@ -77,10 +85,19 @@ public: QLayoutItem* takeAt(int index) override; private: + int horizontalSpacing() const; + int verticalSpacing() const; int doLayout(const QRect& rect, bool testOnly) const; int smartSpacing(QStyle::PixelMetric pm) const; QList itemList; - int m_hSpace; - int m_vSpace; + bool m_dynamic_spacing{}; + int m_hSpaceInitial{-1}; + int m_vSpaceInitial{-1}; + mutable int m_hSpace{-1}; + mutable int m_vSpace{-1}; + + mutable QList m_positions; + mutable int m_rows{}; + mutable int m_cols{}; }; diff --git a/rpcs3/rpcs3qt/flow_widget.cpp b/rpcs3/rpcs3qt/flow_widget.cpp index 281b8e9ee7..10670f5f8f 100644 --- a/rpcs3/rpcs3qt/flow_widget.cpp +++ b/rpcs3/rpcs3qt/flow_widget.cpp @@ -1,8 +1,10 @@ +#include "stdafx.h" #include "flow_widget.h" -#include "flow_layout.h" #include #include +#include +#include flow_widget::flow_widget(QWidget* parent) : QWidget(parent) @@ -11,13 +13,15 @@ flow_widget::flow_widget(QWidget* parent) QWidget* widget = new QWidget(this); widget->setLayout(m_flow_layout); + widget->setObjectName("flow_widget_content"); + widget->setFocusProxy(this); - QScrollArea* scrollArea = new QScrollArea(this); - scrollArea->setWidget(widget); - scrollArea->setWidgetResizable(true); + m_scroll_area = new QScrollArea(this); + m_scroll_area->setWidget(widget); + m_scroll_area->setWidgetResizable(true); QVBoxLayout* layout = new QVBoxLayout(this); - layout->addWidget(scrollArea); + layout->addWidget(m_scroll_area); layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); } @@ -32,6 +36,9 @@ void flow_widget::add_widget(flow_widget_item* widget) { m_widgets << widget; m_flow_layout->addWidget(widget); + + connect(widget, &flow_widget_item::navigate, this, &flow_widget::on_navigate); + connect(widget, &flow_widget_item::focused, this, &flow_widget::on_item_focus); } } @@ -46,19 +53,192 @@ QList& flow_widget::items() return m_widgets; } -void flow_widget_item::paintEvent(QPaintEvent* event) +flow_widget_item* flow_widget::selected_item() const { - QWidget::paintEvent(event); - - if (!got_visible && cb_on_first_visibility) + if (m_selected_index >= 0 && m_selected_index < m_widgets.size()) { - if (QWidget* widget = static_cast(parent())) + return ::at32(m_widgets, m_selected_index); + } + + return nullptr; +} + +QScrollArea* flow_widget::scroll_area() const +{ + return m_scroll_area; +} + +void flow_widget::paintEvent(QPaintEvent* /*event*/) +{ + // Needed for stylesheets to apply to QWidgets + QStyleOption option; + option.initFrom(this); + QPainter painter(this); + style()->drawPrimitive(QStyle::PE_Widget, &option, &painter, this); +} + +int flow_widget::find_item(const flow_layout::position& pos) +{ + if (pos.row < 0 || pos.col < 0) + { + return -1; + } + + const auto& positions = m_flow_layout->positions(); + + for (int i = 0; i < positions.size(); i++) + { + if (const auto& other = ::at32(positions, i); other.row == pos.row && other.col == pos.col) { - if (widget->visibleRegion().intersects(geometry())) + return i; + } + } + + return -1; +} + + flow_layout::position flow_widget::find_item(flow_widget_item* item) +{ + if (item) + { + const auto& item_list = m_flow_layout->item_list(); + const auto& positions = m_flow_layout->positions(); + ensure(item_list.size() == positions.size()); + + for (int i = 0; i < item_list.size(); i++) + { + if (const auto& layout_item = ::at32(item_list, i); layout_item && layout_item->widget() == item) { - got_visible = true; - cb_on_first_visibility(); + return ::at32(positions, i); } } } + + return flow_layout::position{ .row = -1, .col = - 1}; +} + +flow_layout::position flow_widget::find_next_item(flow_layout::position current_pos, flow_navigation value) +{ + if (current_pos.row >= 0 && current_pos.col >= 0 && m_flow_layout->rows() > 0 && m_flow_layout->cols() > 0) + { + switch (value) + { + case flow_navigation::up: + // Go up one row. + if (current_pos.row > 0) + { + current_pos.row--; + } + break; + case flow_navigation::down: + // Go down one row. Beware of last row which might have less columns. + for (const auto& pos : m_flow_layout->positions()) + {; + if (pos.col != current_pos.col) continue; + if (pos.row == current_pos.row + 1) + { + current_pos.row = pos.row; + break; + } + } + break; + case flow_navigation::left: + // Go left one column. + if (current_pos.col > 0) + { + current_pos.col--; + } + break; + case flow_navigation::right: + // Go right one column. Beware of last row which might have less columns. + for (const auto& pos : m_flow_layout->positions()) + { + if (pos.row > current_pos.row) break; + if (pos.row < current_pos.row) continue; + if (pos.col == current_pos.col + 1) + { + current_pos.col = pos.col; + break; + } + } + break; + case flow_navigation::home: + // Go to leftmost column. + current_pos.col = 0; + break; + case flow_navigation::end: + // Go to last column. Beware of last row which might have less columns. + for (const auto& pos : m_flow_layout->positions()) + { + if (pos.row > current_pos.row) break; + if (pos.row < current_pos.row) continue; + current_pos.col = std::max(current_pos.col, pos.col); + } + break; + case flow_navigation::page_up: + // Go to top row. + current_pos.row = 0; + break; + case flow_navigation::page_down: + // Go to bottom row. Beware of last row which might have less columns. + for (const auto& pos : m_flow_layout->positions()) + { + if (pos.col != current_pos.col) continue; + current_pos.row = std::max(current_pos.row, pos.row); + } + break; + } + } + + return current_pos; +} + +void flow_widget::select_item(flow_widget_item* item) +{ + const flow_layout::position selected_pos = find_item(item); + const int selected_index = find_item(selected_pos); + + if (selected_index < 0 || selected_index >= items().size()) + { + m_selected_index = -1; + return; + } + + m_selected_index = selected_index; + Q_EMIT ItemSelectionChanged(m_selected_index); + + for (int i = 0; i < items().size(); i++) + { + if (flow_widget_item* item = items().at(i)) + { + // We need to polish the widgets in order to re-apply any stylesheet changes for the selected property. + item->selected = i == m_selected_index; + item->polish_style(); + } + } + + // Make sure we see the focused widget + m_scroll_area->ensureWidgetVisible(::at32(items(), m_selected_index)); +} + +void flow_widget::on_item_focus() +{ + select_item(static_cast(QObject::sender())); +} + +void flow_widget::on_navigate(flow_navigation value) +{ + const flow_layout::position selected_pos = find_next_item(find_item(static_cast(QObject::sender())), value); + const int selected_index = find_item(selected_pos); + if (selected_index < 0 || selected_index >= items().size()) + { + return; + } + + if (flow_widget_item* item = items().at(selected_index)) + { + item->setFocus(); + } + + m_selected_index = selected_index; } diff --git a/rpcs3/rpcs3qt/flow_widget.h b/rpcs3/rpcs3qt/flow_widget.h index 5389f6cfed..a0166b4dbb 100644 --- a/rpcs3/rpcs3qt/flow_widget.h +++ b/rpcs3/rpcs3qt/flow_widget.h @@ -1,21 +1,16 @@ #pragma once +#include "flow_widget_item.h" +#include "flow_layout.h" + #include - -class flow_layout; - -class flow_widget_item : public QWidget -{ -public: - using QWidget::QWidget; - void paintEvent(QPaintEvent* event) override; - - bool got_visible{}; - std::function cb_on_first_visibility{}; -}; +#include +#include class flow_widget : public QWidget { + Q_OBJECT + public: flow_widget(QWidget* parent); virtual ~flow_widget(); @@ -24,8 +19,28 @@ public: void clear(); QList& items(); + flow_widget_item* selected_item() const; + QScrollArea* scroll_area() const; + + void paintEvent(QPaintEvent* event) override; + +Q_SIGNALS: + void ItemSelectionChanged(int index); + +private Q_SLOTS: + void on_item_focus(); + void on_navigate(flow_navigation value); + +protected: + void select_item(flow_widget_item* item); private: + int find_item(const flow_layout::position& pos); + flow_layout::position find_item(flow_widget_item* item); + flow_layout::position find_next_item(flow_layout::position current_pos, flow_navigation value); + flow_layout* m_flow_layout{}; + QScrollArea* m_scroll_area{}; QList m_widgets{}; + int m_selected_index = -1; }; diff --git a/rpcs3/rpcs3qt/flow_widget_item.cpp b/rpcs3/rpcs3qt/flow_widget_item.cpp new file mode 100644 index 0000000000..cb1c607c42 --- /dev/null +++ b/rpcs3/rpcs3qt/flow_widget_item.cpp @@ -0,0 +1,116 @@ +#include "flow_widget_item.h" + +#include +#include +#include + +void flow_widget_item::polish_style() +{ + style()->unpolish(this); + style()->polish(this); +} + +void flow_widget_item::paintEvent(QPaintEvent* /*event*/) +{ + // Needed for stylesheets to apply to QWidgets + QStyleOption option; + option.initFrom(this); + QPainter painter(this); + style()->drawPrimitive(QStyle::PE_Widget, &option, &painter, this); + + if (!got_visible && cb_on_first_visibility) + { + if (QWidget* widget = static_cast(parent())) + { + if (widget->visibleRegion().intersects(geometry())) + { + got_visible = true; + cb_on_first_visibility(); + } + } + } +} + +void flow_widget_item::focusInEvent(QFocusEvent* event) +{ + QWidget::focusInEvent(event); + + // We need to polish the widgets in order to re-apply any stylesheet changes for the focus property. + polish_style(); + + Q_EMIT focused(); +} + +void flow_widget_item::focusOutEvent(QFocusEvent* event) +{ + QWidget::focusOutEvent(event); + + // We need to polish the widgets in order to re-apply any stylesheet changes for the focus property. + polish_style(); +} + +void flow_widget_item::keyPressEvent(QKeyEvent* event) +{ + if (!event) + { + return; + } + + switch (event->key()) + { + case Qt::Key_Left: + Q_EMIT navigate(flow_navigation::left); + return; + case Qt::Key_Right: + Q_EMIT navigate(flow_navigation::right); + return; + case Qt::Key_Up: + Q_EMIT navigate(flow_navigation::up); + return; + case Qt::Key_Down: + Q_EMIT navigate(flow_navigation::down); + return; + case Qt::Key_Home: + Q_EMIT navigate(flow_navigation::home); + return; + case Qt::Key_End: + Q_EMIT navigate(flow_navigation::end); + return; + case Qt::Key_PageUp: + Q_EMIT navigate(flow_navigation::page_up); + return; + case Qt::Key_PageDown: + Q_EMIT navigate(flow_navigation::page_down); + return; + default: + break; + } + + QWidget::keyPressEvent(event); +} + +bool flow_widget_item::event(QEvent* event) +{ + bool hover_changed = false; + + switch (event->type()) + { + case QEvent::HoverEnter: + hover_changed = setProperty("hover", "true"); + break; + case QEvent::HoverLeave: + hover_changed = setProperty("hover", "false"); + break; + default: + break; + } + + if (hover_changed) + { + // We need to polish the widgets in order to re-apply any stylesheet changes for the custom hover property. + // :hover does not work if we add descendants in the qss, so we need to use a custom property. + polish_style(); + } + + return QWidget::event(event); +} diff --git a/rpcs3/rpcs3qt/flow_widget_item.h b/rpcs3/rpcs3qt/flow_widget_item.h new file mode 100644 index 0000000000..28c7afe378 --- /dev/null +++ b/rpcs3/rpcs3qt/flow_widget_item.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +#include + +enum class flow_navigation +{ + up, + down, + left, + right, + home, + end, + page_up, + page_down +}; + +class flow_widget_item : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(bool hover MEMBER m_hover) // Stylesheet workaround for descendants with parent pseudo state + Q_PROPERTY(bool selected MEMBER selected) // Stylesheet workaround for descendants with parent pseudo state + +public: + using QWidget::QWidget; + + virtual void polish_style(); + + void paintEvent(QPaintEvent* event) override; + void focusInEvent(QFocusEvent* event) override; + void focusOutEvent(QFocusEvent* event) override; + void keyPressEvent(QKeyEvent* event) override; + bool event(QEvent* event) override; + + bool got_visible{}; + bool selected{}; + std::function cb_on_first_visibility{}; + +protected: + bool m_hover{}; + +Q_SIGNALS: + void navigate(flow_navigation value); + void focused(); +}; diff --git a/rpcs3/rpcs3qt/game_list.cpp b/rpcs3/rpcs3qt/game_list.cpp index 979198649a..b7b22eefa5 100644 --- a/rpcs3/rpcs3qt/game_list.cpp +++ b/rpcs3/rpcs3qt/game_list.cpp @@ -1,6 +1,15 @@ +#include "stdafx.h" #include "game_list.h" #include "movie_item.h" +game_list::game_list() : QTableWidget(), game_list_base() +{ + m_icon_ready_callback = [this](const game_info& game) + { + Q_EMIT IconReady(game); + }; +} + void game_list::clear_list() { m_last_hover_item = nullptr; diff --git a/rpcs3/rpcs3qt/game_list.h b/rpcs3/rpcs3qt/game_list.h index ea09b2790d..983ec6490e 100644 --- a/rpcs3/rpcs3qt/game_list.h +++ b/rpcs3/rpcs3qt/game_list.h @@ -3,46 +3,31 @@ #include #include #include -#include -#include "game_compatibility.h" -#include "Emu/GameInfo.h" +#include "game_list_base.h" +#include "util/atomic.hpp" class movie_item; -/* Having the icons associated with the game info simplifies logic internally */ -struct gui_game_info -{ - GameInfo info{}; - QString localized_category; - compat::status compat; - QPixmap icon; - QPixmap pxmap; - bool hasCustomConfig = false; - bool hasCustomPadConfig = false; - bool has_hover_gif = false; - movie_item* item = nullptr; -}; - -typedef std::shared_ptr game_info; -Q_DECLARE_METATYPE(game_info) - /* class used in order to get deselection and hover change if you know a simpler way, tell @Megamouse */ -class game_list : public QTableWidget +class game_list : public QTableWidget, public game_list_base { Q_OBJECT public: - void clear_list(); // Use this instead of clearContents + game_list(); + + void clear_list() override; // Use this instead of clearContents public Q_SLOTS: void FocusAndSelectFirstEntryIfNoneIs(); Q_SIGNALS: void FocusToSearchBar(); + void IconReady(const game_info& game); protected: movie_item* m_last_hover_item = nullptr; diff --git a/rpcs3/rpcs3qt/game_list_base.cpp b/rpcs3/rpcs3qt/game_list_base.cpp new file mode 100644 index 0000000000..4dae7ea8e3 --- /dev/null +++ b/rpcs3/rpcs3qt/game_list_base.cpp @@ -0,0 +1,227 @@ +#include "stdafx.h" +#include "game_list_base.h" +#include "localized.h" + +#include + +#include +#include + +LOG_CHANNEL(game_list_log, "GameList"); + +game_list_base::game_list_base() +{ +} + +void game_list_base::repaint_icons(QList& game_data, const QColor& icon_color, const QSize& icon_size, qreal device_pixel_ratio) +{ + m_icon_size = icon_size; + m_icon_color = icon_color; + + QPixmap placeholder(icon_size * device_pixel_ratio); + placeholder.setDevicePixelRatio(device_pixel_ratio); + placeholder.fill(Qt::transparent); + + for (game_info& game : game_data) + { + game->pxmap = placeholder; + + if (movie_item_base* item = game->item) + { + item->set_icon_load_func([this, game, device_pixel_ratio, cancel = item->icon_loading_aborted()](int) + { + IconLoadFunction(game, device_pixel_ratio, cancel); + }); + + item->call_icon_func(); + } + } +} + +void game_list_base::IconLoadFunction(game_info game, qreal device_pixel_ratio, std::shared_ptr> cancel) +{ + if (cancel && cancel->load()) + { + return; + } + + static std::unordered_set warn_once_list; + static shared_mutex s_mtx; + + if (game->icon.isNull() && (game->info.icon_path.empty() || !game->icon.load(QString::fromStdString(game->info.icon_path)))) + { + if (game_list_log.warning) + { + bool logged = false; + { + std::lock_guard lock(s_mtx); + logged = !warn_once_list.emplace(game->info.icon_path).second; + } + + if (!logged) + { + game_list_log.warning("Could not load image from path %s", QDir(QString::fromStdString(game->info.icon_path)).absolutePath().toStdString()); + } + } + } + + if (!game->item || (cancel && cancel->load())) + { + return; + } + + const QColor color = GetGridCompatibilityColor(game->compat.color); + { + std::lock_guard lock(game->item->pixmap_mutex); + game->pxmap = PaintedPixmap(game->icon, device_pixel_ratio, game->hasCustomConfig, game->hasCustomPadConfig, color); + } + + if (!cancel || !cancel->load()) + { + if (m_icon_ready_callback) + m_icon_ready_callback(game); + } +} + +QPixmap game_list_base::PaintedPixmap(const QPixmap& icon, qreal device_pixel_ratio, bool paint_config_icon, bool paint_pad_config_icon, const QColor& compatibility_color) const +{ + QSize canvas_size(320, 176); + QSize icon_size(icon.size()); + QPoint target_pos; + + if (!icon.isNull()) + { + // Let's upscale the original icon to at least fit into the outer rect of the size of PS3's ICON0.PNG + if (icon_size.width() < 320 || icon_size.height() < 176) + { + icon_size.scale(320, 176, Qt::KeepAspectRatio); + } + + canvas_size = icon_size; + + // Calculate the centered size and position of the icon on our canvas. + if (icon_size.width() != 320 || icon_size.height() != 176) + { + ensure(icon_size.height() > 0); + constexpr double target_ratio = 320.0 / 176.0; // aspect ratio 20:11 + + if ((icon_size.width() / static_cast(icon_size.height())) > target_ratio) + { + canvas_size.setHeight(std::ceil(icon_size.width() / target_ratio)); + } + else + { + canvas_size.setWidth(std::ceil(icon_size.height() * target_ratio)); + } + + target_pos.setX(std::max(0, (canvas_size.width() - icon_size.width()) / 2.0)); + target_pos.setY(std::max(0, (canvas_size.height() - icon_size.height()) / 2.0)); + } + } + + // Create a canvas large enough to fit our entire scaled icon + QPixmap canvas(canvas_size * device_pixel_ratio); + canvas.setDevicePixelRatio(device_pixel_ratio); + canvas.fill(m_icon_color); + + // Create a painter for our canvas + QPainter painter(&canvas); + painter.setRenderHint(QPainter::SmoothPixmapTransform); + + // Draw the icon onto our canvas + if (!icon.isNull()) + { + painter.drawPixmap(target_pos.x(), target_pos.y(), icon_size.width(), icon_size.height(), icon); + } + + // Draw config icons if necessary + if (!m_is_list_layout && (paint_config_icon || paint_pad_config_icon)) + { + const int width = canvas_size.width() * 0.2; + const QPoint origin = QPoint(canvas_size.width() - width, 0); + QString icon_path; + + if (paint_config_icon && paint_pad_config_icon) + { + icon_path = ":/Icons/combo_config_bordered.png"; + } + else if (paint_config_icon) + { + icon_path = ":/Icons/custom_config.png"; + } + else if (paint_pad_config_icon) + { + icon_path = ":/Icons/controllers.png"; + } + + QPixmap custom_config_icon(icon_path); + custom_config_icon.setDevicePixelRatio(device_pixel_ratio); + painter.drawPixmap(origin, custom_config_icon.scaled(QSize(width, width) * device_pixel_ratio, Qt::KeepAspectRatio, Qt::TransformationMode::SmoothTransformation)); + } + + // Draw game compatibility icons if necessary + if (compatibility_color.isValid()) + { + const int size = canvas_size.height() * 0.2; + const int spacing = canvas_size.height() * 0.05; + QColor copyColor = QColor(compatibility_color); + copyColor.setAlpha(215); // ~85% opacity + painter.setRenderHint(QPainter::Antialiasing); + painter.setBrush(QBrush(copyColor)); + painter.setPen(QPen(Qt::black, std::max(canvas_size.width() / 320, canvas_size.height() / 176))); + painter.drawEllipse(spacing, spacing, size, size); + } + + // Finish the painting + painter.end(); + + // Scale and return our final image + return canvas.scaled(m_icon_size * device_pixel_ratio, Qt::KeepAspectRatio, Qt::TransformationMode::SmoothTransformation); +} + +QColor game_list_base::GetGridCompatibilityColor(const QString& string) const +{ + if (m_draw_compat_status_to_grid && !m_is_list_layout) + { + return QColor(string); + } + return QColor(); +} + +std::string game_list_base::GetGameVersion(const game_info& game) +{ + if (game->info.app_ver == Localized().category.unknown.toStdString()) + { + // Fall back to Disc/Pkg Revision + return game->info.version; + } + + return game->info.app_ver; +} + +QIcon game_list_base::GetCustomConfigIcon(const game_info& game) +{ + if (!game) + return {}; + + static const QIcon icon_combo_config_bordered(":/Icons/combo_config_bordered.png"); + static const QIcon icon_custom_config(":/Icons/custom_config.png"); + static const QIcon icon_controllers(":/Icons/controllers.png"); + + if (game->hasCustomConfig && game->hasCustomPadConfig) + { + return icon_combo_config_bordered; + } + + if (game->hasCustomConfig) + { + return icon_custom_config; + } + + if (game->hasCustomPadConfig) + { + return icon_controllers; + } + + return {}; +} diff --git a/rpcs3/rpcs3qt/game_list_base.h b/rpcs3/rpcs3qt/game_list_base.h new file mode 100644 index 0000000000..2d39402281 --- /dev/null +++ b/rpcs3/rpcs3qt/game_list_base.h @@ -0,0 +1,63 @@ +#pragma once + +#include "movie_item_base.h" +#include "game_compatibility.h" +#include "Emu/GameInfo.h" + +#include +#include +#include + +/* Having the icons associated with the game info simplifies logic internally */ +struct gui_game_info +{ + GameInfo info{}; + QString localized_category; + compat::status compat; + QPixmap icon; + QPixmap pxmap; + bool hasCustomConfig = false; + bool hasCustomPadConfig = false; + bool has_hover_gif = false; + movie_item_base* item = nullptr; +}; + +typedef std::shared_ptr game_info; +Q_DECLARE_METATYPE(game_info) + +class game_list_base +{ +public: + game_list_base(); + + virtual void clear_list(){}; + virtual void populate( + [[maybe_unused]] const std::vector& game_data, + [[maybe_unused]] const QMap& notes_map, + [[maybe_unused]] const QMap& title_map, + [[maybe_unused]] const std::string& selected_item_id, + [[maybe_unused]] bool play_hover_movies){}; + + void set_icon_size(QSize size) { m_icon_size = std::move(size); } + void set_icon_color(QColor color) { m_icon_color = std::move(color); } + void set_draw_compat_status_to_grid(bool enabled) { m_draw_compat_status_to_grid = enabled; } + + virtual void repaint_icons(QList& game_data, const QColor& icon_color, const QSize& icon_size, qreal device_pixel_ratio); + + // Returns the visible version string in the game list + static std::string GetGameVersion(const game_info& game); + + /** Sets the custom config icon. */ + static QIcon GetCustomConfigIcon(const game_info& game); + +protected: + void IconLoadFunction(game_info game, qreal device_pixel_ratio, std::shared_ptr> cancel); + QPixmap PaintedPixmap(const QPixmap& icon, qreal device_pixel_ratio, bool paint_config_icon = false, bool paint_pad_config_icon = false, const QColor& compatibility_color = {}) const; + QColor GetGridCompatibilityColor(const QString& string) const; + + std::function m_icon_ready_callback{}; + bool m_draw_compat_status_to_grid{}; + bool m_is_list_layout{}; + QSize m_icon_size{}; + QColor m_icon_color{}; +}; diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index 86f374b25d..57c98e61cd 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -2,7 +2,6 @@ #include "qt_utils.h" #include "settings_dialog.h" #include "pad_settings_dialog.h" -#include "game_list_delegate.h" #include "custom_table_widget_item.h" #include "input_dialog.h" #include "localized.h" @@ -10,8 +9,10 @@ #include "persistent_settings.h" #include "emu_settings.h" #include "gui_settings.h" -#include "game_list.h" +#include "game_list_delegate.h" +#include "game_list_table.h" #include "game_list_grid.h" +#include "game_list_grid_item.h" #include "patch_manager_dialog.h" #include "Emu/Memory/vm.h" @@ -89,39 +90,26 @@ game_list_frame::game_list_frame(std::shared_ptr gui_settings, std m_game_dock->setWindowFlags(Qt::Widget); setWidget(m_game_dock); - m_game_grid = new game_list_grid(QSize(), m_icon_color, m_margin_factor, m_text_factor, false); + m_game_grid = new game_list_grid(); + m_game_grid->installEventFilter(this); + m_game_grid->scroll_area()->verticalScrollBar()->installEventFilter(this); - m_game_list = new game_list(); - m_game_list->setShowGrid(false); - m_game_list->setItemDelegate(new game_list_delegate(m_game_list)); - m_game_list->setEditTriggers(QAbstractItemView::NoEditTriggers); - m_game_list->setSelectionBehavior(QAbstractItemView::SelectRows); - m_game_list->setSelectionMode(QAbstractItemView::SingleSelection); - m_game_list->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); - m_game_list->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); - m_game_list->verticalScrollBar()->installEventFilter(this); - m_game_list->verticalScrollBar()->setSingleStep(20); - m_game_list->horizontalScrollBar()->setSingleStep(20); - m_game_list->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); - m_game_list->verticalHeader()->setVisible(false); - m_game_list->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); - m_game_list->horizontalHeader()->setHighlightSections(false); - m_game_list->horizontalHeader()->setSortIndicatorShown(true); - m_game_list->horizontalHeader()->setStretchLastSection(true); - m_game_list->horizontalHeader()->setDefaultSectionSize(150); - m_game_list->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft); - m_game_list->setContextMenuPolicy(Qt::CustomContextMenu); - m_game_list->setAlternatingRowColors(true); - m_game_list->installEventFilter(this); - m_game_list->setColumnCount(gui::column_count); - m_game_list->setMouseTracking(true); + m_game_list = new game_list_table(this, m_persistent_settings); m_game_compat = new game_compatibility(m_gui_settings, this); m_central_widget = new QStackedWidget(this); m_central_widget->addWidget(m_game_list); m_central_widget->addWidget(m_game_grid); - m_central_widget->setCurrentWidget(m_is_list_layout ? m_game_list : m_game_grid); + + if (m_is_list_layout) + { + m_central_widget->setCurrentWidget(m_game_list); + } + else + { + m_central_widget->setCurrentWidget(m_game_grid); + } m_game_dock->setCentralWidget(m_central_widget); @@ -173,25 +161,10 @@ game_list_frame::game_list_frame(std::shared_ptr gui_settings, std m_serials.clear(); m_games.pop_all(); }); - connect(this, &game_list_frame::IconReady, this, [this](const game_info& game) - { - if (!game || !game->item) return; - game->item->call_icon_func(); - }); - connect(this, &game_list_frame::SizeOnDiskReady, this, [this](const game_info& game) - { - if (!m_is_list_layout || !game || !game->item) return; - if (QTableWidgetItem* size_item = m_game_list->item(game->item->row(), gui::column_dir_size)) - { - const u64& game_size = game->info.size_on_disk; - size_item->setText(game_size != umax ? gui::utils::format_byte_size(game_size) : tr("Unknown")); - size_item->setData(Qt::UserRole, QVariant::fromValue(game_size)); - } - }); connect(m_game_list, &QTableWidget::customContextMenuRequested, this, &game_list_frame::ShowContextMenu); connect(m_game_list, &QTableWidget::itemSelectionChanged, this, &game_list_frame::ItemSelectionChangedSlot); - connect(m_game_list, &QTableWidget::itemDoubleClicked, this, &game_list_frame::doubleClickedSlot); + connect(m_game_list, &QTableWidget::itemDoubleClicked, this, QOverload::of(&game_list_frame::doubleClickedSlot)); connect(m_game_list->horizontalHeader(), &QHeaderView::sectionClicked, this, &game_list_frame::OnColClicked); connect(m_game_list->horizontalHeader(), &QHeaderView::customContextMenuRequested, this, [this](const QPoint& pos) @@ -201,9 +174,9 @@ game_list_frame::game_list_frame(std::shared_ptr gui_settings, std configure->exec(m_game_list->horizontalHeader()->viewport()->mapToGlobal(pos)); }); - connect(m_game_grid, &QTableWidget::customContextMenuRequested, this, &game_list_frame::ShowContextMenu); - connect(m_game_grid, &QTableWidget::itemSelectionChanged, this, &game_list_frame::ItemSelectionChangedSlot); - connect(m_game_grid, &QTableWidget::itemDoubleClicked, this, &game_list_frame::doubleClickedSlot); + connect(m_game_grid, &QWidget::customContextMenuRequested, this, &game_list_frame::ShowContextMenu); + connect(m_game_grid, &game_list_grid::ItemSelectionChanged, this, &game_list_frame::NotifyGameSelection); + connect(m_game_grid, &game_list_grid::ItemDoubleClicked, this, QOverload::of(&game_list_frame::doubleClickedSlot)); connect(m_game_compat, &game_compatibility::DownloadStarted, this, [this]() { @@ -222,7 +195,7 @@ game_list_frame::game_list_frame(std::shared_ptr gui_settings, std }); connect(m_game_list, &game_list::FocusToSearchBar, this, &game_list_frame::FocusToSearchBar); - connect(m_game_grid, &game_list::FocusToSearchBar, this, &game_list_frame::FocusToSearchBar); + connect(m_game_grid, &game_list_grid::FocusToSearchBar, this, &game_list_frame::FocusToSearchBar); for (int col = 0; col < m_columnActs.count(); ++col) { @@ -249,7 +222,7 @@ game_list_frame::game_list_frame(std::shared_ptr gui_settings, std if (checked) // handle hidden columns that have zero width after showing them (stuck between others) { - FixNarrowColumns(); + m_game_list->fix_narrow_columns(); } }); } @@ -283,48 +256,6 @@ game_list_frame::~game_list_frame() SaveSettings(); } -void game_list_frame::FixNarrowColumns() const -{ - QApplication::processEvents(); - - // handle columns (other than the icon column) that have zero width after showing them (stuck between others) - for (int col = 1; col < m_columnActs.count(); ++col) - { - if (m_game_list->isColumnHidden(col)) - { - continue; - } - - if (m_game_list->columnWidth(col) <= m_game_list->horizontalHeader()->minimumSectionSize()) - { - m_game_list->setColumnWidth(col, m_game_list->horizontalHeader()->minimumSectionSize()); - } - } -} - -void game_list_frame::ResizeColumnsToContents(int spacing) const -{ - if (!m_game_list) - { - return; - } - - m_game_list->verticalHeader()->resizeSections(QHeaderView::ResizeMode::ResizeToContents); - m_game_list->horizontalHeader()->resizeSections(QHeaderView::ResizeMode::ResizeToContents); - - // Make non-icon columns slighty bigger for better visuals - for (int i = 1; i < m_game_list->columnCount(); i++) - { - if (m_game_list->isColumnHidden(i)) - { - continue; - } - - const int size = m_game_list->horizontalHeader()->sectionSize(i) + spacing; - m_game_list->horizontalHeader()->resizeSection(i, size); - } -} - void game_list_frame::OnColClicked(int col) { if (col == gui::column_icon) return; // Don't "sort" icons. @@ -342,11 +273,11 @@ void game_list_frame::OnColClicked(int col) m_gui_settings->SetValue(gui::gl_sortAsc, m_col_sort_order == Qt::AscendingOrder); m_gui_settings->SetValue(gui::gl_sortCol, col); - SortGameList(); + m_game_list->sort(m_game_data.count(), m_sort_column, m_col_sort_order); } // Get visibility of entries -bool game_list_frame::IsEntryVisible(const game_info& game, bool search_fallback) +bool game_list_frame::IsEntryVisible(const game_info& game, bool search_fallback) const { const auto matches_category = [&]() { @@ -363,78 +294,6 @@ bool game_list_frame::IsEntryVisible(const game_info& game, bool search_fallback return is_visible && matches_category() && SearchMatchesApp(qstr(game->info.name), serial, search_fallback); } -void game_list_frame::SortGameList() -{ - // Back-up old header sizes to handle unwanted column resize in case of zero search results - const int old_row_count = m_game_list->rowCount(); - const int old_game_count = m_game_data.count(); - - std::vector column_widths(m_game_list->columnCount()); - for (int i = 0; i < m_game_list->columnCount(); i++) - { - column_widths[i] = m_game_list->columnWidth(i); - } - - // Sorting resizes hidden columns, so unhide them as a workaround - std::vector columns_to_hide; - - for (int i = 0; i < m_game_list->columnCount(); i++) - { - if (m_game_list->isColumnHidden(i)) - { - m_game_list->setColumnHidden(i, false); - columns_to_hide.push_back(i); - } - } - - // Sort the list by column and sort order - m_game_list->sortByColumn(m_sort_column, m_col_sort_order); - - // Hide columns again - for (int col : columns_to_hide) - { - m_game_list->setColumnHidden(col, true); - } - - // Don't resize the columns if no game is shown to preserve the header settings - if (!m_game_list->rowCount()) - { - for (int i = 0; i < m_game_list->columnCount(); i++) - { - m_game_list->setColumnWidth(i, column_widths[i]); - } - - m_game_list->horizontalHeader()->setSectionResizeMode(gui::column_icon, QHeaderView::Fixed); - return; - } - - // Fixate vertical header and row height - m_game_list->verticalHeader()->setMinimumSectionSize(m_icon_size.height()); - m_game_list->verticalHeader()->setMaximumSectionSize(m_icon_size.height()); - m_game_list->resizeRowsToContents(); - - // Resize columns if the game list was empty before - if (!old_row_count && !old_game_count) - { - ResizeColumnsToContents(); - } - else - { - m_game_list->resizeColumnToContents(gui::column_icon); - } - - // Fixate icon column - m_game_list->horizontalHeader()->setSectionResizeMode(gui::column_icon, QHeaderView::Fixed); - - // Shorten the last section to remove horizontal scrollbar if possible - m_game_list->resizeColumnToContents(gui::column_count - 1); -} - -QString game_list_frame::GetLastPlayedBySerial(const QString& serial) const -{ - return m_persistent_settings->GetLastPlayed(serial); -} - std::string game_list_frame::GetCacheDirBySerial(const std::string& serial) { return rpcs3::utils::get_cache_dir() + serial; @@ -574,11 +433,43 @@ void game_list_frame::Refresh(const bool from_drive, const bool scroll_after) // Fill Game List / Game Grid + const std::string selected_item = CurrentSelectionPath(); + + // Release old data + for (const auto& game : m_game_data) + { + game->item = nullptr; + } + + // Get list of matching apps + std::vector matching_apps; + + for (const auto& app : m_game_data) + { + if (IsEntryVisible(app)) + { + matching_apps.push_back(app); + } + } + + // Fallback is not needed when at least one entry is visible + if (matching_apps.empty()) + { + for (const auto& app : m_game_data) + { + if (IsEntryVisible(app, true)) + { + matching_apps.push_back(app); + } + } + } + if (m_is_list_layout) { + m_game_grid->clear_list(); const int scroll_position = m_game_list->verticalScrollBar()->value(); - PopulateGameList(); - SortGameList(); + m_game_list->populate(matching_apps, m_notes, m_titles, selected_item, m_play_hover_movies); + m_game_list->sort(m_game_data.count(), m_sort_column, m_col_sort_order); RepaintIcons(); if (scroll_after) @@ -592,6 +483,8 @@ void game_list_frame::Refresh(const bool from_drive, const bool scroll_after) } else { + m_game_list->clear_list(); + m_game_grid->populate(matching_apps, m_notes, m_titles, selected_item, m_play_hover_movies); RepaintIcons(); } } @@ -919,26 +812,7 @@ void game_list_frame::OnRefreshFinished() if (!std::exchange(m_initial_refresh_done, true)) { - // Resize to fit and get the ideal icon column width - ResizeColumnsToContents(); - const int icon_column_width = m_game_list->columnWidth(gui::column_icon); - - // Restore header layout from last session - const QByteArray state = m_gui_settings->GetValue(gui::gl_state).toByteArray(); - if (!m_game_list->horizontalHeader()->restoreState(state) && m_game_list->rowCount()) - { - // Nothing to do - } - - // Make sure no columns are squished - FixNarrowColumns(); - - // Make sure that the icon column is large enough for the actual items. - // This is important if the list appeared as empty when closing the software before. - m_game_list->horizontalHeader()->resizeSection(gui::column_icon, icon_column_width); - - // Save new header state - m_game_list->horizontalHeader()->restoreState(m_game_list->horizontalHeader()->saveState()); + m_game_list->restore_layout(m_gui_settings->GetValue(gui::gl_state).toByteArray()); } if (m_progress_dialog_timer) @@ -1000,8 +874,11 @@ void game_list_frame::doubleClickedSlot(QTableWidgetItem *item) return; } - const game_info game = GetGameInfoByMode(item); + doubleClickedSlot(GetGameInfoByMode(item)); +} +void game_list_frame::doubleClickedSlot(const game_info& game) +{ if (!game) { return; @@ -1022,10 +899,6 @@ void game_list_frame::ItemSelectionChangedSlot() game = GetGameInfoByMode(item); } } - else if (const auto item = m_game_grid->currentItem(); item && item->isSelected()) - { - game = GetGameInfoByMode(item); - } Q_EMIT NotifyGameSelection(game); } @@ -1138,21 +1011,20 @@ void game_list_frame::CreateShortcuts(const game_info& gameinfo, const std::set< void game_list_frame::ShowContextMenu(const QPoint &pos) { QPoint global_pos; - QTableWidgetItem* item; + game_info gameinfo; if (m_is_list_layout) { - item = m_game_list->item(m_game_list->indexAt(pos).row(), gui::column_icon); + QTableWidgetItem* item = m_game_list->item(m_game_list->indexAt(pos).row(), gui::column_icon); global_pos = m_game_list->viewport()->mapToGlobal(pos); + gameinfo = GetGameInfoFromItem(item); } - else + else if (game_list_grid_item* item = static_cast(m_game_grid->selected_item())) { - const QModelIndex mi = m_game_grid->indexAt(pos); - item = m_game_grid->item(mi.row(), mi.column()); - global_pos = m_game_grid->viewport()->mapToGlobal(pos); + gameinfo = item->game(); + global_pos = m_game_grid->mapToGlobal(pos); } - game_info gameinfo = GetGameInfoFromItem(item); if (!gameinfo) { return; @@ -1614,10 +1486,10 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) { if (game) { - games[game->info.serial].insert(game_list_frame::GetGameVersion(game)); + games[game->info.serial].insert(game_list::GetGameVersion(game)); } } - patch_manager_dialog patch_manager(m_gui_settings, games, gameinfo->info.serial, GetGameVersion(gameinfo), this); + patch_manager_dialog patch_manager(m_gui_settings, games, gameinfo->info.serial, game_list::GetGameVersion(gameinfo), this); patch_manager.exec(); }); connect(open_game_folder, &QAction::triggered, this, [current_game]() @@ -2272,130 +2144,6 @@ void game_list_frame::BatchRemoveShaderCaches() QApplication::beep(); } -QPixmap game_list_frame::PaintedPixmap(const QPixmap& icon, bool paint_config_icon, bool paint_pad_config_icon, const QColor& compatibility_color) const -{ - const qreal device_pixel_ratio = devicePixelRatioF(); - QSize canvas_size(320, 176); - QSize icon_size(icon.size()); - QPoint target_pos; - - if (!icon.isNull()) - { - // Let's upscale the original icon to at least fit into the outer rect of the size of PS3's ICON0.PNG - if (icon_size.width() < 320 || icon_size.height() < 176) - { - icon_size.scale(320, 176, Qt::KeepAspectRatio); - } - - canvas_size = icon_size; - - // Calculate the centered size and position of the icon on our canvas. - if (icon_size.width() != 320 || icon_size.height() != 176) - { - ensure(icon_size.height() > 0); - constexpr double target_ratio = 320.0 / 176.0; // aspect ratio 20:11 - - if ((icon_size.width() / static_cast(icon_size.height())) > target_ratio) - { - canvas_size.setHeight(std::ceil(icon_size.width() / target_ratio)); - } - else - { - canvas_size.setWidth(std::ceil(icon_size.height() * target_ratio)); - } - - target_pos.setX(std::max(0, (canvas_size.width() - icon_size.width()) / 2.0)); - target_pos.setY(std::max(0, (canvas_size.height() - icon_size.height()) / 2.0)); - } - } - - // Create a canvas large enough to fit our entire scaled icon - QPixmap canvas(canvas_size * device_pixel_ratio); - canvas.setDevicePixelRatio(device_pixel_ratio); - canvas.fill(m_icon_color); - - // Create a painter for our canvas - QPainter painter(&canvas); - painter.setRenderHint(QPainter::SmoothPixmapTransform); - - // Draw the icon onto our canvas - if (!icon.isNull()) - { - painter.drawPixmap(target_pos.x(), target_pos.y(), icon_size.width(), icon_size.height(), icon); - } - - // Draw config icons if necessary - if (!m_is_list_layout && (paint_config_icon || paint_pad_config_icon)) - { - const int width = canvas_size.width() * 0.2; - const QPoint origin = QPoint(canvas_size.width() - width, 0); - QString icon_path; - - if (paint_config_icon && paint_pad_config_icon) - { - icon_path = ":/Icons/combo_config_bordered.png"; - } - else if (paint_config_icon) - { - icon_path = ":/Icons/custom_config.png"; - } - else if (paint_pad_config_icon) - { - icon_path = ":/Icons/controllers.png"; - } - - QPixmap custom_config_icon(icon_path); - custom_config_icon.setDevicePixelRatio(device_pixel_ratio); - painter.drawPixmap(origin, custom_config_icon.scaled(QSize(width, width) * device_pixel_ratio, Qt::KeepAspectRatio, Qt::TransformationMode::SmoothTransformation)); - } - - // Draw game compatibility icons if necessary - if (compatibility_color.isValid()) - { - const int size = canvas_size.height() * 0.2; - const int spacing = canvas_size.height() * 0.05; - QColor copyColor = QColor(compatibility_color); - copyColor.setAlpha(215); // ~85% opacity - painter.setRenderHint(QPainter::Antialiasing); - painter.setBrush(QBrush(copyColor)); - painter.setPen(QPen(Qt::black, std::max(canvas_size.width() / 320, canvas_size.height() / 176))); - painter.drawEllipse(spacing, spacing, size, size); - } - - // Finish the painting - painter.end(); - - // Scale and return our final image - return canvas.scaled(m_icon_size * device_pixel_ratio, Qt::KeepAspectRatio, Qt::TransformationMode::SmoothTransformation); -} - -void game_list_frame::SetCustomConfigIcon(QTableWidgetItem* title_item, const game_info& game) -{ - if (!title_item || !game) - return; - - static QIcon icon_combo_config_bordered(":/Icons/combo_config_bordered.png"); - static QIcon icon_custom_config(":/Icons/custom_config.png"); - static QIcon icon_controllers(":/Icons/controllers.png"); - - if (game->hasCustomConfig && game->hasCustomPadConfig) - { - title_item->setIcon(icon_combo_config_bordered); - } - else if (game->hasCustomConfig) - { - title_item->setIcon(icon_custom_config); - } - else if (game->hasCustomPadConfig) - { - title_item->setIcon(icon_controllers); - } - else if (!title_item->icon().isNull()) - { - title_item->setIcon({}); - } -} - void game_list_frame::ShowCustomConfigIcon(const game_info& game) { if (!game) @@ -2416,15 +2164,7 @@ void game_list_frame::ShowCustomConfigIcon(const game_info& game) } } - const QString q_serial = qstr(game->info.serial); - - for (int row = 0; row < m_game_list->rowCount(); ++row) - { - if (const auto item = m_game_list->item(row, gui::column_serial); item && item->text() == q_serial) - { - SetCustomConfigIcon(m_game_list->item(row, gui::column_name), game); - } - } + m_game_list->set_custom_config_icon(game); RepaintIcons(); } @@ -2457,52 +2197,12 @@ void game_list_frame::RepaintIcons(const bool& from_settings) if (m_is_list_layout) { - QPixmap placeholder(m_icon_size); - placeholder.fill(Qt::transparent); - - for (game_info& game : m_game_data) - { - game->pxmap = placeholder; - - if (movie_item* item = game->item) - { - item->set_icon_load_func([this, game, cancel = item->icon_loading_aborted()](int) - { - IconLoadFunction(game, cancel); - }); - item->call_icon_func(); - } - } - - // Fixate vertical header and row height - m_game_list->verticalHeader()->setMinimumSectionSize(m_icon_size.height()); - m_game_list->verticalHeader()->setMaximumSectionSize(m_icon_size.height()); - - // Resize the icon column - m_game_list->resizeColumnToContents(gui::column_icon); - - // Shorten the last section to remove horizontal scrollbar if possible - m_game_list->resizeColumnToContents(gui::column_count - 1); + m_game_list->repaint_icons(m_game_data, m_icon_color, m_icon_size, devicePixelRatioF()); } else { - // The game grid needs to be recreated from scratch - int games_per_row = 0; - - if (m_icon_size.width() > 0 && m_icon_size.height() > 0) - { - games_per_row = width() / (m_icon_size.width() + m_icon_size.width() * m_game_grid->getMarginFactor() * 2); - } - - const int scroll_position = m_game_grid->verticalScrollBar()->value(); - PopulateGameGrid(games_per_row, m_icon_size, m_icon_color); - connect(m_game_grid, &QTableWidget::customContextMenuRequested, this, &game_list_frame::ShowContextMenu); - connect(m_game_grid, &QTableWidget::itemSelectionChanged, this, &game_list_frame::ItemSelectionChangedSlot); - connect(m_game_grid, &QTableWidget::itemDoubleClicked, this, &game_list_frame::doubleClickedSlot); - connect(m_game_grid, &game_list::FocusToSearchBar, this, &game_list_frame::FocusToSearchBar); - m_central_widget->addWidget(m_game_grid); - m_central_widget->setCurrentWidget(m_game_grid); - m_game_grid->verticalScrollBar()->setValue(scroll_position); + m_game_grid->set_draw_compat_status_to_grid(m_draw_compat_status_to_grid); + m_game_grid->repaint_icons(m_game_data, m_icon_color, m_icon_size, devicePixelRatioF()); } } @@ -2520,7 +2220,14 @@ void game_list_frame::SetListMode(const bool& is_list) Refresh(); - m_central_widget->setCurrentWidget(m_is_list_layout ? m_game_list : m_game_grid); + if (m_is_list_layout) + { + m_central_widget->setCurrentWidget(m_game_list); + } + else + { + m_central_widget->setCurrentWidget(m_game_grid); + } } void game_list_frame::SetSearchText(const QString& text) @@ -2533,21 +2240,17 @@ void game_list_frame::FocusAndSelectFirstEntryIfNoneIs() { if (m_is_list_layout) { - if (!m_game_list) + if (m_game_list) { - return; + m_game_list->FocusAndSelectFirstEntryIfNoneIs(); } - - m_game_list->FocusAndSelectFirstEntryIfNoneIs(); } else { - if (!m_game_grid) + if (m_game_grid) { - return; + m_game_grid->FocusAndSelectFirstEntryIfNoneIs(); } - - m_game_grid->FocusAndSelectFirstEntryIfNoneIs(); } } @@ -2557,19 +2260,10 @@ void game_list_frame::closeEvent(QCloseEvent *event) Q_EMIT GameListFrameClosed(); } -void game_list_frame::resizeEvent(QResizeEvent *event) -{ - if (!m_is_list_layout) - { - Refresh(false, m_game_grid->selectedItems().count()); - } - QDockWidget::resizeEvent(event); -} - bool game_list_frame::eventFilter(QObject *object, QEvent *event) { // Zoom gamelist/gamegrid - if (event->type() == QEvent::Wheel && (object == m_game_list->verticalScrollBar() || object == m_game_grid->verticalScrollBar())) + if (event->type() == QEvent::Wheel && (object == m_game_list->verticalScrollBar() || object == m_game_grid->scroll_area()->verticalScrollBar())) { QWheelEvent *wheel_event = static_cast(event); @@ -2602,17 +2296,21 @@ bool game_list_frame::eventFilter(QObject *object, QEvent *event) { if (key_event->key() == Qt::Key_Enter || key_event->key() == Qt::Key_Return) { - QTableWidgetItem* item; + game_info gameinfo{}; if (object == m_game_list) - item = m_game_list->item(m_game_list->currentRow(), gui::column_icon); - else - item = m_game_grid->currentItem(); + { + QTableWidgetItem* item = m_game_list->item(m_game_list->currentRow(), gui::column_icon); - if (!item || !item->isSelected()) - return false; + if (!item || !item->isSelected()) + return false; - const game_info gameinfo = GetGameInfoFromItem(item); + gameinfo = GetGameInfoFromItem(item); + } + else if (game_list_grid_item* item = static_cast(m_game_grid->selected_item())) + { + gameinfo = item->game(); + } if (!gameinfo) return false; @@ -2628,325 +2326,6 @@ bool game_list_frame::eventFilter(QObject *object, QEvent *event) return QDockWidget::eventFilter(object, event); } -/** - Cleans and readds entries to table widget in UI. -*/ -void game_list_frame::PopulateGameList() -{ - int selected_row = -1; - - const std::string selected_item = CurrentSelectionPath(); - - // Release old data - for (const auto& game : m_game_data) - { - game->item = nullptr; - } - - m_game_grid->clear_list(); - m_game_list->clear_list(); - - m_game_list->setRowCount(m_game_data.size()); - - // Default locale. Uses current Qt application language. - const QLocale locale{}; - const Localized localized; - - const QString game_icon_path = m_play_hover_movies ? qstr(fs::get_config_dir() + "/Icons/game_icons/") : ""; - - int row = 0; - int index = -1; - - // Fallback is not needed when at least one entry is visible - const bool use_search_fallback = std::none_of(m_game_data.begin(), m_game_data.end(), [this](auto& game){ return IsEntryVisible(game); }); - - for (const auto& game : m_game_data) - { - index++; - - if (!IsEntryVisible(game, use_search_fallback)) - { - continue; - } - - const QString serial = qstr(game->info.serial); - const QString title = m_titles.value(serial, qstr(game->info.name)); - const QString notes = m_notes.value(serial); - - // Icon - custom_table_widget_item* icon_item = new custom_table_widget_item; - game->item = icon_item; - - icon_item->set_icon_func([this, icon_item, game](int) - { - if (!icon_item || !game) - { - return; - } - - if (std::shared_ptr movie = icon_item->movie(); movie && icon_item->get_active()) - { - icon_item->setData(Qt::DecorationRole, movie->currentPixmap().scaled(m_icon_size, Qt::KeepAspectRatio)); - } - else - { - std::lock_guard lock(icon_item->pixmap_mutex); - - icon_item->setData(Qt::DecorationRole, game->pxmap); - - if (!game->has_hover_gif) - { - game->pxmap = {}; - } - - if (movie) - { - movie->stop(); - } - } - }); - - icon_item->set_size_calc_func([this, game, cancel = icon_item->size_on_disk_loading_aborted(), dev_flash = g_cfg_vfs.get_dev_flash()]() - { - if (game && game->info.size_on_disk == umax && (!cancel || !cancel->load())) - { - if (game->info.path.starts_with(dev_flash)) - { - // Do not report size of apps inside /dev_flash (it does not make sense to do so) - game->info.size_on_disk = 0; - } - else - { - game->info.size_on_disk = fs::get_dir_size(game->info.path, 1, cancel.get()); - } - - if (!cancel || !cancel->load()) - { - Q_EMIT SizeOnDiskReady(game); - return; - } - } - }); - - if (m_play_hover_movies && game->has_hover_gif) - { - icon_item->init_movie(game_icon_path % serial % "/hover.gif"); - } - - icon_item->setData(Qt::UserRole, index, true); - icon_item->setData(gui::custom_roles::game_role, QVariant::fromValue(game)); - - // Title - custom_table_widget_item* title_item = new custom_table_widget_item(title); - SetCustomConfigIcon(title_item, game); - - // Serial - custom_table_widget_item* serial_item = new custom_table_widget_item(game->info.serial); - - if (!notes.isEmpty()) - { - const QString tool_tip = tr("%0 [%1]\n\nNotes:\n%2").arg(title).arg(serial).arg(notes); - title_item->setToolTip(tool_tip); - serial_item->setToolTip(tool_tip); - } - - // Move Support (http://www.psdevwiki.com/ps3/PARAM.SFO#ATTRIBUTE) - const bool supports_move = game->info.attr & 0x800000; - - // Compatibility - custom_table_widget_item* compat_item = new custom_table_widget_item; - compat_item->setText(game->compat.text % (game->compat.date.isEmpty() ? QStringLiteral("") : " (" % game->compat.date % ")")); - compat_item->setData(Qt::UserRole, game->compat.index, true); - compat_item->setToolTip(game->compat.tooltip); - if (!game->compat.color.isEmpty()) - { - compat_item->setData(Qt::DecorationRole, gui::utils::circle_pixmap(game->compat.color, devicePixelRatioF() * 2)); - } - - // Version - QString app_version = qstr(GetGameVersion(game)); - - if (game->info.bootable && !game->compat.latest_version.isEmpty()) - { - f64 top_ver = 0.0, app_ver = 0.0; - const bool unknown = app_version == localized.category.unknown; - const bool ok_app = !unknown && try_to_float(&app_ver, sstr(app_version), ::std::numeric_limits::min(), ::std::numeric_limits::max()); - const bool ok_top = !unknown && try_to_float(&top_ver, sstr(game->compat.latest_version), ::std::numeric_limits::min(), ::std::numeric_limits::max()); - - // If the app is bootable and the compat database contains info about the latest patch version: - // add a hint for available software updates if the app version is unknown or lower than the latest version. - if (unknown || (ok_top && ok_app && top_ver > app_ver)) - { - app_version = tr("%0 (Update available: %1)").arg(app_version, game->compat.latest_version); - } - } - - // Playtimes - const quint64 elapsed_ms = m_persistent_settings->GetPlaytime(serial); - - // Last played (support outdated values) - QDateTime last_played; - const QString last_played_str = GetLastPlayedBySerial(serial); - - if (!last_played_str.isEmpty()) - { - last_played = QDateTime::fromString(last_played_str, gui::persistent::last_played_date_format); - - if (!last_played.isValid()) - { - last_played = QDateTime::fromString(last_played_str, gui::persistent::last_played_date_format_old); - } - } - - const u64 game_size = game->info.size_on_disk; - - m_game_list->setItem(row, gui::column_icon, icon_item); - m_game_list->setItem(row, gui::column_name, title_item); - m_game_list->setItem(row, gui::column_serial, serial_item); - m_game_list->setItem(row, gui::column_firmware, new custom_table_widget_item(game->info.fw)); - m_game_list->setItem(row, gui::column_version, new custom_table_widget_item(app_version)); - m_game_list->setItem(row, gui::column_category, new custom_table_widget_item(game->localized_category)); - m_game_list->setItem(row, gui::column_path, new custom_table_widget_item(game->info.path)); - m_game_list->setItem(row, gui::column_move, new custom_table_widget_item(sstr(supports_move ? tr("Supported") : tr("Not Supported")), Qt::UserRole, !supports_move)); - m_game_list->setItem(row, gui::column_resolution, new custom_table_widget_item(GetStringFromU32(game->info.resolution, localized.resolution.mode, true))); - m_game_list->setItem(row, gui::column_sound, new custom_table_widget_item(GetStringFromU32(game->info.sound_format, localized.sound.format, true))); - m_game_list->setItem(row, gui::column_parental, new custom_table_widget_item(GetStringFromU32(game->info.parental_lvl, localized.parental.level), Qt::UserRole, game->info.parental_lvl)); - m_game_list->setItem(row, gui::column_last_play, new custom_table_widget_item(locale.toString(last_played, last_played >= QDateTime::currentDateTime().addDays(-7) ? gui::persistent::last_played_date_with_time_of_day_format : gui::persistent::last_played_date_format_new), Qt::UserRole, last_played)); - m_game_list->setItem(row, gui::column_playtime, new custom_table_widget_item(elapsed_ms == 0 ? tr("Never played") : localized.GetVerboseTimeByMs(elapsed_ms), Qt::UserRole, elapsed_ms)); - m_game_list->setItem(row, gui::column_compat, compat_item); - m_game_list->setItem(row, gui::column_dir_size, new custom_table_widget_item(game_size != umax ? gui::utils::format_byte_size(game_size) : tr("Unknown"), Qt::UserRole, QVariant::fromValue(game_size))); - - if (selected_item == game->info.path + game->info.icon_path) - { - selected_row = row; - } - - row++; - } - - m_game_list->setRowCount(row); - m_game_list->selectRow(selected_row); -} - -void game_list_frame::PopulateGameGrid(int maxCols, const QSize& image_size, const QColor& image_color) -{ - int r = 0; - int c = 0; - - const std::string selected_item = CurrentSelectionPath(); - - // Release old data - for (const auto& game : m_game_data) - { - game->item = nullptr; - } - - m_game_list->clear_list(); - m_game_grid->deleteLater(); - - const bool show_text = m_icon_size_index > gui::gl_max_slider_pos * 2 / 5; - - if (m_icon_size_index < gui::gl_max_slider_pos * 2 / 3) - { - m_game_grid = new game_list_grid(image_size, image_color, m_margin_factor, m_text_factor * 2, show_text); - } - else - { - m_game_grid = new game_list_grid(image_size, image_color, m_margin_factor, m_text_factor, show_text); - } - - // Get list of matching apps - std::vector matching_apps; - - for (const auto& app : m_game_data) - { - if (IsEntryVisible(app)) - { - matching_apps.push_back(app); - } - } - - // Fallback is not needed when at least one entry is visible - if (matching_apps.empty()) - { - for (const auto& app : m_game_data) - { - if (IsEntryVisible(app, true)) - { - matching_apps.push_back(app); - } - } - } - - const int entries = static_cast(matching_apps.size()); - - // Edge cases! - if (entries == 0) - { // For whatever reason, 0%x is division by zero. Absolute nonsense by definition of modulus. But, I'll acquiesce. - return; - } - - maxCols = std::clamp(maxCols, 1, entries); - - const int needs_extra_row = (entries % maxCols) != 0; - const int max_rows = needs_extra_row + entries / maxCols; - m_game_grid->setRowCount(max_rows); - m_game_grid->setColumnCount(maxCols); - - const QString game_icon_path = m_play_hover_movies ? qstr(fs::get_config_dir() + "/Icons/game_icons/") : ""; - - for (const game_info& app : matching_apps) - { - const QString serial = qstr(app->info.serial); - const QString title = m_titles.value(serial, qstr(app->info.name)); - const QString notes = m_notes.value(serial); - - movie_item* item = m_game_grid->addItem(app, title, (m_play_hover_movies && app->has_hover_gif) ? (game_icon_path % serial % "/hover.gif") : QStringLiteral(""), r, c); - ensure(item); - app->item = item; - item->setData(gui::game_role, QVariant::fromValue(app)); - item->set_icon_load_func([this, app, cancel = item->icon_loading_aborted()](int) - { - IconLoadFunction(app, cancel); - }); - - if (!notes.isEmpty()) - { - item->setToolTip(tr("%0 [%1]\n\nNotes:\n%2").arg(title).arg(serial).arg(notes)); - } - else - { - item->setToolTip(tr("%0 [%1]").arg(title).arg(serial)); - } - - if (selected_item == app->info.path + app->info.icon_path) - { - m_game_grid->setCurrentItem(item); - } - - if (++c >= maxCols) - { - c = 0; - r++; - } - } - - if (c != 0) - { // if left over games exist -- if empty entries exist - for (int col = c; col < maxCols; ++col) - { - movie_item* empty_item = new movie_item(); - empty_item->setFlags(Qt::NoItemFlags); - m_game_grid->setItem(r, col, empty_item); - } - } - - m_game_grid->resizeColumnsToContents(); - m_game_grid->resizeRowsToContents(); - m_game_grid->installEventFilter(this); - m_game_grid->verticalScrollBar()->installEventFilter(this); -} - /** * Returns false if the game should be hidden because it doesn't match search term in toolbar. */ @@ -2995,32 +2374,32 @@ std::string game_list_frame::CurrentSelectionPath() { std::string selection; - QTableWidgetItem* item = nullptr; + game_info game{}; if (m_old_layout_is_list) { if (!m_game_list->selectedItems().isEmpty()) { - item = m_game_list->item(m_game_list->currentRow(), 0); + if (QTableWidgetItem* item = m_game_list->item(m_game_list->currentRow(), 0)) + { + if (const QVariant var = item->data(gui::game_role); var.canConvert()) + { + game = var.value(); + } + } } } else if (m_game_grid) { - if (!m_game_grid->selectedItems().isEmpty()) + if (game_list_grid_item* item = static_cast(m_game_grid->selected_item())) { - item = m_game_grid->currentItem(); + game = item->game(); } } - if (item) + if (game) { - if (const QVariant var = item->data(gui::game_role); var.canConvert()) - { - if (const game_info game = var.value()) - { - selection = game->info.path + game->info.icon_path; - } - } + selection = game->info.path + game->info.icon_path; } m_old_layout_is_list = m_is_list_layout; @@ -3028,36 +2407,6 @@ std::string game_list_frame::CurrentSelectionPath() return selection; } -std::string game_list_frame::GetStringFromU32(const u32& key, const std::map& map, bool combined) -{ - QStringList string; - - if (combined) - { - for (const auto& item : map) - { - if (key & item.first) - { - string << item.second; - } - } - } - else - { - if (map.find(key) != map.end()) - { - string << ::at32(map, key); - } - } - - if (string.isEmpty()) - { - string << tr("Unknown"); - } - - return sstr(string.join(", ")); -} - game_info game_list_frame::GetGameInfoByMode(const QTableWidgetItem* item) const { if (!item) @@ -3089,15 +2438,6 @@ game_info game_list_frame::GetGameInfoFromItem(const QTableWidgetItem* item) return var.value(); } -QColor game_list_frame::getGridCompatibilityColor(const QString& string) const -{ - if (m_draw_compat_status_to_grid && !m_is_list_layout) - { - return QColor(string); - } - return QColor(); -} - void game_list_frame::SetShowCompatibilityInGrid(bool show) { m_draw_compat_status_to_grid = show; @@ -3130,61 +2470,6 @@ const QList& game_list_frame::GetGameInfo() const return m_game_data; } -std::string game_list_frame::GetGameVersion(const game_info& game) -{ - if (game->info.app_ver == sstr(Localized().category.unknown)) - { - // Fall back to Disc/Pkg Revision - return game->info.version; - } - - return game->info.app_ver; -} - -void game_list_frame::IconLoadFunction(game_info game, std::shared_ptr> cancel) -{ - if (cancel && cancel->load()) - { - return; - } - - static std::unordered_set warn_once_list; - static shared_mutex s_mtx; - - if (game->icon.isNull() && (game->info.icon_path.empty() || !game->icon.load(qstr(game->info.icon_path)))) - { - if (game_list_log.warning) - { - bool logged = false; - { - std::lock_guard lock(s_mtx); - logged = !warn_once_list.emplace(game->info.icon_path).second; - } - - if (!logged) - { - game_list_log.warning("Could not load image from path %s", sstr(QDir(qstr(game->info.icon_path)).absolutePath())); - } - } - } - - if (!game->item || (cancel && cancel->load())) - { - return; - } - - const QColor color = getGridCompatibilityColor(game->compat.color); - { - std::lock_guard lock(game->item->pixmap_mutex); - game->pxmap = PaintedPixmap(game->icon, game->hasCustomConfig, game->hasCustomPadConfig, color); - } - - if (!cancel || !cancel->load()) - { - Q_EMIT IconReady(game); - } -} - void game_list_frame::WaitAndAbortRepaintThreads() { for (const game_info& game : m_game_data) diff --git a/rpcs3/rpcs3qt/game_list_frame.h b/rpcs3/rpcs3qt/game_list_frame.h index 0a166fc608..50a18282e5 100644 --- a/rpcs3/rpcs3qt/game_list_frame.h +++ b/rpcs3/rpcs3qt/game_list_frame.h @@ -19,6 +19,7 @@ #include #include +class game_list_table; class game_list_grid; class gui_settings; class emu_settings; @@ -33,12 +34,6 @@ public: explicit game_list_frame(std::shared_ptr gui_settings, std::shared_ptr emu_settings, std::shared_ptr persistent_settings, QWidget* parent = nullptr); ~game_list_frame(); - /** Fix columns with width smaller than the minimal section size */ - void FixNarrowColumns() const; - - /** Resizes the columns to their contents and adds a small spacing */ - void ResizeColumnsToContents(int spacing = 20) const; - /** Refresh the gamelist with/without loading game data from files. Public so that main frame can refresh after vfs or install */ void Refresh(const bool from_drive = false, const bool scroll_after = true); @@ -63,11 +58,10 @@ public: const QList& GetGameInfo() const; - // Returns the visible version string in the game list - static std::string GetGameVersion(const game_info& game); - void CreateShortcuts(const game_info& gameinfo, const std::set& locations); + bool IsEntryVisible(const game_info& game, bool search_fallback = false) const; + public Q_SLOTS: void BatchCreatePPUCaches(); void BatchRemovePPUCaches(); @@ -89,6 +83,7 @@ private Q_SLOTS: void OnColClicked(int col); void ShowContextMenu(const QPoint &pos); void doubleClickedSlot(QTableWidgetItem *item); + void doubleClickedSlot(const game_info& game); void ItemSelectionChangedSlot(); Q_SIGNALS: void GameListFrameClosed(); @@ -96,28 +91,15 @@ Q_SIGNALS: void RequestBoot(const game_info& game, cfg_mode config_mode = cfg_mode::custom, const std::string& config_path = "", const std::string& savestate = ""); void RequestIconSizeChange(const int& val); void NotifyEmuSettingsChange(); - void IconReady(const game_info& game); - void SizeOnDiskReady(const game_info& game); void FocusToSearchBar(); protected: /** Override inherited method from Qt to allow signalling when close happened.*/ void closeEvent(QCloseEvent* event) override; - void resizeEvent(QResizeEvent *event) override; bool eventFilter(QObject *object, QEvent *event) override; private: void push_path(const std::string& path, std::vector& legit_paths); - QPixmap PaintedPixmap(const QPixmap& icon, bool paint_config_icon = false, bool paint_pad_config_icon = false, const QColor& color = QColor()) const; - QColor getGridCompatibilityColor(const QString& string) const; - void IconLoadFunction(game_info game, std::shared_ptr> cancel); - - /** Sets the custom config icon. Only call this for list title items. */ - void SetCustomConfigIcon(QTableWidgetItem* title_item, const game_info& game); void ShowCustomConfigIcon(const game_info& game); - void PopulateGameList(); - void PopulateGameGrid(int maxCols, const QSize& image_size, const QColor& image_color); - bool IsEntryVisible(const game_info& game, bool search_fallback = false); - void SortGameList(); bool SearchMatchesApp(const QString& name, const QString& serial, bool fallback = false) const; bool RemoveCustomConfiguration(const std::string& title_id, const game_info& game = nullptr, bool is_interactive = false); @@ -128,11 +110,9 @@ private: static bool CreatePPUCache(const std::string& path, const std::string& serial = {}); static bool CreatePPUCache(const game_info& game); - QString GetLastPlayedBySerial(const QString& serial) const; static std::string GetCacheDirBySerial(const std::string& serial); static std::string GetDataDirBySerial(const std::string& serial); std::string CurrentSelectionPath(); - static std::string GetStringFromU32(const u32& key, const std::map& map, bool combined = false); game_info GetGameInfoByMode(const QTableWidgetItem* item) const; static game_info GetGameInfoFromItem(const QTableWidgetItem* item); @@ -148,13 +128,13 @@ private: game_list_grid* m_game_grid = nullptr; // Game List - game_list* m_game_list = nullptr; + game_list_table* m_game_list = nullptr; game_compatibility* m_game_compat = nullptr; progress_dialog* m_progress_dialog = nullptr; QTimer* m_progress_dialog_timer = nullptr; QList m_columnActs; - Qt::SortOrder m_col_sort_order; - int m_sort_column; + Qt::SortOrder m_col_sort_order{}; + int m_sort_column{}; bool m_initial_refresh_done = false; QMap m_notes; QMap m_titles; diff --git a/rpcs3/rpcs3qt/game_list_grid.cpp b/rpcs3/rpcs3qt/game_list_grid.cpp index daa1e56ea8..23e39fc1ec 100644 --- a/rpcs3/rpcs3qt/game_list_grid.cpp +++ b/rpcs3/rpcs3qt/game_list_grid.cpp @@ -1,154 +1,212 @@ +#include "stdafx.h" #include "game_list_grid.h" -#include "game_list_grid_delegate.h" +#include "game_list_grid_item.h" #include "movie_item.h" +#include "gui_settings.h" #include "qt_utils.h" +#include "Utilities/File.h" -#include -#include +#include +#include -game_list_grid::game_list_grid(const QSize& icon_size, QColor icon_color, const qreal& margin_factor, const qreal& text_factor, const bool& showText) - : game_list() - , m_icon_size(icon_size) - , m_icon_color(std::move(icon_color)) - , m_margin_factor(margin_factor) - , m_text_factor(text_factor) - , m_text_enabled(showText) +game_list_grid::game_list_grid() + : flow_widget(nullptr), game_list_base() { - setObjectName("game_grid"); - - QSize item_size; - if (m_text_enabled) - { - item_size = m_icon_size + QSize(m_icon_size.width() * m_margin_factor * 2, m_icon_size.height() * m_margin_factor * (m_text_factor + 1)); - } - else - { - item_size = m_icon_size + m_icon_size * m_margin_factor * 2; - } - - grid_item_delegate = new game_list_grid_delegate(item_size, m_margin_factor, m_text_factor, this); - setItemDelegate(grid_item_delegate); - setEditTriggers(QAbstractItemView::NoEditTriggers); - setSelectionBehavior(QAbstractItemView::SelectItems); - setSelectionMode(QAbstractItemView::SingleSelection); - setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); - setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); - verticalScrollBar()->setSingleStep(20); - horizontalScrollBar()->setSingleStep(20); + setObjectName("game_list_grid"); setContextMenuPolicy(Qt::CustomContextMenu); - verticalHeader()->setVisible(false); - horizontalHeader()->setVisible(false); - setShowGrid(false); - setMouseTracking(true); -} -void game_list_grid::enableText(const bool& enabled) -{ - m_text_enabled = enabled; -} - -void game_list_grid::setIconSize(const QSize& size) const -{ - if (m_text_enabled) + m_icon_ready_callback = [this](const game_info& game) { - grid_item_delegate->setItemSize(size + QSize(size.width() * m_margin_factor * 2, size.height() * m_margin_factor * (m_text_factor + 1))); - } - else + Q_EMIT IconReady(game); + }; + + connect(this, &game_list_grid::IconReady, this, [this](const game_info& game) { - grid_item_delegate->setItemSize(size + size * m_margin_factor * 2); - } -} + if (!game || !game->item) return; + game->item->call_icon_func(); + }, Qt::QueuedConnection); // The default 'AutoConnection' doesn't seem to work in this specific case... -movie_item* game_list_grid::addItem(const game_info& app, const QString& name, const QString& movie_path, const int& row, const int& col) -{ - // create item with expanded image, title and position - movie_item* item = new movie_item; - - item->set_icon_func([this, app, item](int) + connect(this, &flow_widget::ItemSelectionChanged, this, [this](int index) { - if (!item) + if (game_list_grid_item* item = static_cast(::at32(items(), index))) { - return; + Q_EMIT ItemSelectionChanged(item->game()); } - - const qreal device_pixel_ratio = devicePixelRatioF(); - - // define size of expanded image, which is raw image size + margins - QSizeF exp_size_f; - if (m_text_enabled) - { - exp_size_f = m_icon_size + QSizeF(m_icon_size.width() * m_margin_factor * 2, m_icon_size.height() * m_margin_factor * (m_text_factor + 1)); - } - else - { - exp_size_f = m_icon_size + m_icon_size * m_margin_factor * 2; - } - - std::shared_ptr movie = item->movie(); - const bool draw_movie_frame = movie && movie->isValid() && item->get_active(); - const QSize exp_size = (exp_size_f * device_pixel_ratio).toSize(); - - // create empty canvas for expanded image - QImage exp_img(exp_size, QImage::Format_ARGB32); - exp_img.setDevicePixelRatio(device_pixel_ratio); - exp_img.fill(Qt::transparent); - - // define offset for raw image placement - QPoint offset(m_icon_size.width() * m_margin_factor, m_icon_size.height() * m_margin_factor); - - // place raw image inside expanded image - QPainter painter(&exp_img); - painter.setRenderHint(QPainter::SmoothPixmapTransform); - - if (draw_movie_frame) - { - const QPixmap scaled_movie_frame = movie->currentPixmap().scaled(m_icon_size, Qt::KeepAspectRatio, Qt::SmoothTransformation); - offset += QPoint(m_icon_size.width() / 2 - scaled_movie_frame.width() / 2, - m_icon_size.height() / 2 - scaled_movie_frame.height() / 2); - painter.drawPixmap(offset, scaled_movie_frame); - } - else - { - // create background for image - QImage bg_img(app->pxmap.size(), QImage::Format_ARGB32); - bg_img.setDevicePixelRatio(device_pixel_ratio); - bg_img.fill(m_icon_color); - - painter.drawImage(offset, bg_img); - painter.drawPixmap(offset, app->pxmap); - - if (!app->has_hover_gif) - { - app->pxmap = {}; - } - - if (movie) - { - movie->stop(); - } - } - - painter.end(); - - // create item with expanded image, title and position - item->setData(Qt::ItemDataRole::DecorationRole, QPixmap::fromImage(exp_img)); }); - - if (!movie_path.isEmpty()) - { - item->init_movie(movie_path); - } - - if (m_text_enabled) - { - item->setData(Qt::ItemDataRole::DisplayRole, name); - } - - setItem(row, col, item); - return item; } -qreal game_list_grid::getMarginFactor() const +void game_list_grid::clear_list() { - return m_margin_factor; + clear(); +} + +void game_list_grid::populate( + const std::vector& game_data, + const QMap& notes_map, + const QMap& title_map, + const std::string& selected_item_id, + bool play_hover_movies) +{ + clear_list(); + + const QString game_icon_path = play_hover_movies ? QString::fromStdString(fs::get_config_dir() + "/Icons/game_icons/") : ""; + game_list_grid_item* selected_item = nullptr; + + blockSignals(true); + + for (const auto& game : game_data) + { + const QString serial = QString::fromStdString(game->info.serial); + const QString title = title_map.value(serial, QString::fromStdString(game->info.name)).simplified(); + const QString notes = notes_map.value(serial); + + game_list_grid_item* item = new game_list_grid_item(this, game, title); + item->installEventFilter(this); + item->setFocusPolicy(Qt::StrongFocus); + + game->item = item; + + if (notes.isEmpty()) + { + item->setToolTip(tr("%0 [%1]").arg(title).arg(serial)); + } + else + { + item->setToolTip(tr("%0 [%1]\n\nNotes:\n%2").arg(title).arg(serial).arg(notes)); + } + + item->set_icon_func([this, item, game](int) + { + if (!item || !game) + { + return; + } + + if (std::shared_ptr movie = item->movie(); movie && item->get_active()) + { + item->set_icon(gui::utils::get_centered_pixmap(movie->currentPixmap(), m_icon_size, 0, 0, 1.0, Qt::FastTransformation)); + } + else + { + std::lock_guard lock(item->pixmap_mutex); + + item->set_icon(game->pxmap); + + if (!game->has_hover_gif) + { + game->pxmap = {}; + } + + if (movie) + { + movie->stop(); + } + } + }); + + if (play_hover_movies && game->has_hover_gif) + { + item->init_movie(game_icon_path % serial % "/hover.gif"); + } + + if (selected_item_id == game->info.path + game->info.icon_path) + { + selected_item = item; + } + + add_widget(item); + } + + blockSignals(false); + + // Update layout before setting focus on the selected item + show(); + + QApplication::processEvents(); + + select_item(selected_item); +} + +void game_list_grid::repaint_icons(QList& game_data, const QColor& icon_color, const QSize& icon_size, qreal device_pixel_ratio) +{ + m_icon_size = icon_size; + m_icon_color = icon_color; + + QPixmap placeholder(icon_size * device_pixel_ratio); + placeholder.setDevicePixelRatio(device_pixel_ratio); + placeholder.fill(Qt::transparent); + + const bool show_title = m_icon_size.width() > (gui::gl_icon_size_medium.width() + gui::gl_icon_size_small.width()) / 2; + + for (game_info& game : game_data) + { + if (game_list_grid_item* item = static_cast(game->item)) + { + if (item->icon_loading()) + { + // We already have an icon. Simply set the icon size to let the label scale itself in a quick and dirty fashion. + item->set_icon_size(m_icon_size); + } + else + { + // We don't have an icon. Set a placeholder to initialize the layout. + game->pxmap = placeholder; + item->call_icon_func(); + } + + item->set_icon_load_func([this, game, device_pixel_ratio, cancel = item->icon_loading_aborted()](int) + { + IconLoadFunction(game, device_pixel_ratio, cancel); + }); + + item->adjust_size(); + item->show_title(show_title); + item->got_visible = false; + } + } +} + +void game_list_grid::FocusAndSelectFirstEntryIfNoneIs() +{ + if (items().empty() == false) + { + items().first()->setFocus(); + } +} + +bool game_list_grid::eventFilter(QObject* watched, QEvent* event) +{ + if (!event) + { + return false; + } + + if (event->type() == QEvent::MouseButtonDblClick && static_cast(event)->button() == Qt::LeftButton) + { + if (game_list_grid_item* item = static_cast(watched)) + { + Q_EMIT ItemDoubleClicked(item->game()); + return true; + } + } + + return false; +} + +void game_list_grid::keyPressEvent(QKeyEvent* event) +{ + if (!event) + { + return; + } + + const auto modifiers = event->modifiers(); + + if (modifiers == Qt::ControlModifier && event->key() == Qt::Key_F && !event->isAutoRepeat()) + { + Q_EMIT FocusToSearchBar(); + return; + } + + flow_widget::keyPressEvent(event); } diff --git a/rpcs3/rpcs3qt/game_list_grid.h b/rpcs3/rpcs3qt/game_list_grid.h index f2c7bd36f9..318d932baf 100644 --- a/rpcs3/rpcs3qt/game_list_grid.h +++ b/rpcs3/rpcs3qt/game_list_grid.h @@ -1,28 +1,37 @@ #pragma once -#include "game_list.h" +#include "game_list_base.h" +#include "flow_widget.h" -class game_list_grid_delegate; +#include -class game_list_grid : public game_list +class game_list_grid : public flow_widget, public game_list_base { Q_OBJECT - QSize m_icon_size; - QColor m_icon_color; - qreal m_margin_factor; - qreal m_text_factor; - bool m_text_enabled = true; - public: - explicit game_list_grid(const QSize& icon_size, QColor icon_color, const qreal& margin_factor, const qreal& text_factor, const bool& showText); + explicit game_list_grid(); - void enableText(const bool& enabled); - void setIconSize(const QSize& size) const; - movie_item* addItem(const game_info& app, const QString& name, const QString& movie_path, const int& row, const int& col); + void clear_list() override; - [[nodiscard]] qreal getMarginFactor() const; + void populate( + const std::vector& game_data, + const QMap& notes_map, + const QMap& title_map, + const std::string& selected_item_id, + bool play_hover_movies) override; -private: - game_list_grid_delegate* grid_item_delegate; + void repaint_icons(QList& game_data, const QColor& icon_color, const QSize& icon_size, qreal device_pixel_ratio) override; + + bool eventFilter(QObject* watched, QEvent* event) override; + void keyPressEvent(QKeyEvent* event) override; + +public Q_SLOTS: + void FocusAndSelectFirstEntryIfNoneIs(); + +Q_SIGNALS: + void FocusToSearchBar(); + void ItemDoubleClicked(const game_info& game); + void ItemSelectionChanged(const game_info& game); + void IconReady(const game_info& game); }; diff --git a/rpcs3/rpcs3qt/game_list_grid_delegate.cpp b/rpcs3/rpcs3qt/game_list_grid_delegate.cpp deleted file mode 100644 index 5032ffa8a4..0000000000 --- a/rpcs3/rpcs3qt/game_list_grid_delegate.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "game_list_grid_delegate.h" -#include "movie_item.h" - -#include - -game_list_grid_delegate::game_list_grid_delegate(const QSize& size, const qreal& margin_factor, const qreal& text_factor, QObject *parent) - : QStyledItemDelegate(parent), m_size(size), m_margin_factor(margin_factor), m_text_factor(text_factor) -{ -} - -void game_list_grid_delegate::initStyleOption(QStyleOptionViewItem* option, const QModelIndex& index) const -{ - Q_UNUSED(index) - - // Remove the focus frame around selected items - option->state &= ~QStyle::State_HasFocus; - - // Call initStyleOption without a model index, since we want to paint the relevant data ourselves - QStyledItemDelegate::initStyleOption(option, QModelIndex()); -} - -void game_list_grid_delegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const -{ - const QRect r = option.rect; - - painter->setRenderHints(QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); - painter->eraseRect(r); - - // Paint from our stylesheet - QStyledItemDelegate::paint(painter, option, index); - - // Check if the item is visible - if (const QTableWidget* table = static_cast(parent())) - { - if (movie_item* item = static_cast(table->item(index.row(), index.column()))) - { - if (!table->visibleRegion().intersects(table->visualItemRect(item))) - { - // Skip all further actions if the item is not visible - return; - } - - if (!item->icon_loading()) - { - item->call_icon_load_func(index.row()); - } - } - } - - // Get title and image - const QPixmap image = qvariant_cast(index.data(Qt::DecorationRole)); - const QString title = index.data(Qt::DisplayRole).toString(); - - // image - if (image.isNull() == false) - { - painter->drawPixmap(option.rect, image); - } - - const int h = r.height() / (1 + m_margin_factor + m_margin_factor * m_text_factor); - const int height = r.height() - h - h * m_margin_factor; - const int top = r.bottom() - height; - - // title - if (option.state & QStyle::State_Selected) - { - painter->setPen(QPen(option.palette.color(QPalette::HighlightedText), 1, Qt::SolidLine)); - } - else - { - painter->setPen(QPen(option.palette.color(QPalette::WindowText), 1, Qt::SolidLine)); - } - - painter->setFont(option.font); - painter->drawText(QRect(r.left(), top, r.width(), height), +Qt::TextWordWrap | +Qt::AlignCenter, title); -} - -QSize game_list_grid_delegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const -{ - Q_UNUSED(option) - Q_UNUSED(index) - return m_size; -} - -void game_list_grid_delegate::setItemSize(const QSize& size) -{ - m_size = size; -} diff --git a/rpcs3/rpcs3qt/game_list_grid_delegate.h b/rpcs3/rpcs3qt/game_list_grid_delegate.h deleted file mode 100644 index 7ec6ecde76..0000000000 --- a/rpcs3/rpcs3qt/game_list_grid_delegate.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include -#include - -class game_list_grid_delegate : public QStyledItemDelegate -{ -public: - game_list_grid_delegate(const QSize& imageSize, const qreal& margin_factor, const qreal& margin_ratio, QObject *parent = nullptr); - - void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const override; - void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; - QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const override; - void setItemSize(const QSize& size); -private: - QSize m_size; - qreal m_margin_factor; - qreal m_text_factor; -}; diff --git a/rpcs3/rpcs3qt/game_list_grid_item.cpp b/rpcs3/rpcs3qt/game_list_grid_item.cpp new file mode 100644 index 0000000000..9b369761e7 --- /dev/null +++ b/rpcs3/rpcs3qt/game_list_grid_item.cpp @@ -0,0 +1,86 @@ +#include "game_list_grid_item.h" + +#include +#include + +game_list_grid_item::game_list_grid_item(QWidget* parent, game_info game, const QString& title) + : flow_widget_item(parent), movie_item_base(), m_game(std::move(game)) +{ + setObjectName("game_list_grid_item"); + + cb_on_first_visibility = [this]() + { + if (!icon_loading()) + { + call_icon_load_func(0); + } + }; + + m_icon_label = new QLabel(this); + m_icon_label->setObjectName("game_list_grid_item_icon_label"); + m_icon_label->setAttribute(Qt::WA_TranslucentBackground); + m_icon_label->setScaledContents(true); + + m_title_label = new QLabel(title, this); + m_title_label->setObjectName("game_list_grid_item_title_label"); + m_title_label->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); + m_title_label->setWordWrap(true); + m_title_label->setVisible(false); + + QVBoxLayout* layout = new QVBoxLayout(this); + layout->addWidget(m_icon_label, 1); + layout->addWidget(m_title_label, 0); + + setLayout(layout); +} + +void game_list_grid_item::set_icon_size(const QSize& size) +{ + m_icon_size = size; +} + +void game_list_grid_item::set_icon(const QPixmap& pixmap) +{ + m_icon_size = pixmap.size() / devicePixelRatioF(); + m_icon_label->setPixmap(pixmap); +} + +void game_list_grid_item::adjust_size() +{ + m_icon_label->setMinimumSize(m_icon_size); + m_icon_label->setMaximumSize(m_icon_size); + m_title_label->setMaximumWidth(m_icon_size.width()); +} + +void game_list_grid_item::show_title(bool visible) +{ + if (m_title_label) + { + m_title_label->setVisible(visible); + } +} + +void game_list_grid_item::polish_style() +{ + flow_widget_item::polish_style(); + + m_title_label->style()->unpolish(m_title_label); + m_title_label->style()->polish(m_title_label); +} + +bool game_list_grid_item::event(QEvent* event) +{ + switch (event->type()) + { + case QEvent::HoverEnter: + set_active(true); + break; + case QEvent::HoverLeave: + set_active(false); + break; + default: + break; + } + + return flow_widget_item::event(event); +} diff --git a/rpcs3/rpcs3qt/game_list_grid_item.h b/rpcs3/rpcs3qt/game_list_grid_item.h new file mode 100644 index 0000000000..7738548238 --- /dev/null +++ b/rpcs3/rpcs3qt/game_list_grid_item.h @@ -0,0 +1,36 @@ +#pragma once + +#include "flow_widget_item.h" +#include "movie_item_base.h" +#include "game_list_base.h" + +#include + +class game_list_grid_item : public flow_widget_item, public movie_item_base +{ + Q_OBJECT + +public: + game_list_grid_item(QWidget* parent, game_info game, const QString& title); + + void set_icon_size(const QSize& size); + void set_icon(const QPixmap& pixmap); + void adjust_size(); + + const game_info& game() const + { + return m_game; + } + + void show_title(bool visible); + + void polish_style() override; + + bool event(QEvent* event) override; + +private: + QSize m_icon_size{}; + QLabel* m_icon_label{}; + QLabel* m_title_label{}; + game_info m_game{}; +}; diff --git a/rpcs3/rpcs3qt/game_list_table.cpp b/rpcs3/rpcs3qt/game_list_table.cpp new file mode 100644 index 0000000000..8f64391699 --- /dev/null +++ b/rpcs3/rpcs3qt/game_list_table.cpp @@ -0,0 +1,415 @@ +#include "stdafx.h" +#include "game_list_table.h" +#include "game_list_delegate.h" +#include "game_list_frame.h" +#include "gui_settings.h" +#include "localized.h" +#include "custom_table_widget_item.h" +#include "persistent_settings.h" +#include "qt_utils.h" + +#include "Emu/vfs_config.h" +#include "Utilities/StrUtil.h" + +#include +#include +#include +#include + +game_list_table::game_list_table(game_list_frame* frame, std::shared_ptr persistent_settings) + : game_list(), m_game_list_frame(frame), m_persistent_settings(std::move(persistent_settings)) +{ + m_is_list_layout = true; + + setShowGrid(false); + setItemDelegate(new game_list_delegate(this)); + setEditTriggers(QAbstractItemView::NoEditTriggers); + setSelectionBehavior(QAbstractItemView::SelectRows); + setSelectionMode(QAbstractItemView::SingleSelection); + setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); + verticalScrollBar()->installEventFilter(this); + verticalScrollBar()->setSingleStep(20); + horizontalScrollBar()->setSingleStep(20); + verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); + verticalHeader()->setVisible(false); + horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); + horizontalHeader()->setHighlightSections(false); + horizontalHeader()->setSortIndicatorShown(true); + horizontalHeader()->setStretchLastSection(true); + horizontalHeader()->setDefaultSectionSize(150); + horizontalHeader()->setDefaultAlignment(Qt::AlignLeft); + setContextMenuPolicy(Qt::CustomContextMenu); + setAlternatingRowColors(true); + installEventFilter(this); + setColumnCount(gui::column_count); + setMouseTracking(true); + + connect(this, &game_list_table::size_on_disk_ready, this, [this](const game_info& game) + { + if (!game || !game->item) return; + if (QTableWidgetItem* size_item = item(static_cast(game->item)->row(), gui::column_dir_size)) + { + const u64& game_size = game->info.size_on_disk; + size_item->setText(game_size != umax ? gui::utils::format_byte_size(game_size) : tr("Unknown")); + size_item->setData(Qt::UserRole, QVariant::fromValue(game_size)); + } + }); + + connect(this, &game_list::IconReady, this, [this](const game_info& game) + { + if (!game || !game->item) return; + game->item->call_icon_func(); + }); +} + +void game_list_table::restore_layout(const QByteArray& state) +{ + // Resize to fit and get the ideal icon column width + resize_columns_to_contents(); + const int icon_column_width = columnWidth(gui::column_icon); + + // Restore header layout from last session + if (!horizontalHeader()->restoreState(state) && rowCount()) + { + // Nothing to do + } + + // Make sure no columns are squished + fix_narrow_columns(); + + // Make sure that the icon column is large enough for the actual items. + // This is important if the list appeared as empty when closing the software before. + horizontalHeader()->resizeSection(gui::column_icon, icon_column_width); + + // Save new header state + horizontalHeader()->restoreState(horizontalHeader()->saveState()); +} + +void game_list_table::fix_narrow_columns() +{ + QApplication::processEvents(); + + // handle columns (other than the icon column) that have zero width after showing them (stuck between others) + for (int col = 1; col < columnCount(); ++col) + { + if (isColumnHidden(col)) + { + continue; + } + + if (columnWidth(col) <= horizontalHeader()->minimumSectionSize()) + { + setColumnWidth(col, horizontalHeader()->minimumSectionSize()); + } + } +} + +void game_list_table::resize_columns_to_contents(int spacing) +{ + verticalHeader()->resizeSections(QHeaderView::ResizeMode::ResizeToContents); + horizontalHeader()->resizeSections(QHeaderView::ResizeMode::ResizeToContents); + + // Make non-icon columns slighty bigger for better visuals + for (int i = 1; i < columnCount(); i++) + { + if (isColumnHidden(i)) + { + continue; + } + + const int size = horizontalHeader()->sectionSize(i) + spacing; + horizontalHeader()->resizeSection(i, size); + } +} + +void game_list_table::adjust_icon_column() +{ + // Fixate vertical header and row height + verticalHeader()->setMinimumSectionSize(m_icon_size.height()); + verticalHeader()->setMaximumSectionSize(m_icon_size.height()); + + // Resize the icon column + resizeColumnToContents(gui::column_icon); + + // Shorten the last section to remove horizontal scrollbar if possible + resizeColumnToContents(gui::column_count - 1); +} + +void game_list_table::sort(int game_count, int sort_column, Qt::SortOrder col_sort_order) +{ + // Back-up old header sizes to handle unwanted column resize in case of zero search results + const int old_row_count = rowCount(); + const int old_game_count = game_count; + + std::vector column_widths(columnCount()); + for (int i = 0; i < columnCount(); i++) + { + column_widths[i] = columnWidth(i); + } + + // Sorting resizes hidden columns, so unhide them as a workaround + std::vector columns_to_hide; + + for (int i = 0; i < columnCount(); i++) + { + if (isColumnHidden(i)) + { + setColumnHidden(i, false); + columns_to_hide.push_back(i); + } + } + + // Sort the list by column and sort order + sortByColumn(sort_column, col_sort_order); + + // Hide columns again + for (int col : columns_to_hide) + { + setColumnHidden(col, true); + } + + // Don't resize the columns if no game is shown to preserve the header settings + if (!rowCount()) + { + for (int i = 0; i < columnCount(); i++) + { + setColumnWidth(i, column_widths[i]); + } + + horizontalHeader()->setSectionResizeMode(gui::column_icon, QHeaderView::Fixed); + return; + } + + // Fixate vertical header and row height + verticalHeader()->setMinimumSectionSize(m_icon_size.height()); + verticalHeader()->setMaximumSectionSize(m_icon_size.height()); + resizeRowsToContents(); + + // Resize columns if the game list was empty before + if (!old_row_count && !old_game_count) + { + resize_columns_to_contents(); + } + else + { + resizeColumnToContents(gui::column_icon); + } + + // Fixate icon column + horizontalHeader()->setSectionResizeMode(gui::column_icon, QHeaderView::Fixed); + + // Shorten the last section to remove horizontal scrollbar if possible + resizeColumnToContents(gui::column_count - 1); +} + +void game_list_table::set_custom_config_icon(const game_info& game) +{ + if (!game) + { + return; + } + + const QString serial = QString::fromStdString(game->info.serial); + + for (int row = 0; row < rowCount(); ++row) + { + if (QTableWidgetItem* title_item = item(row, gui::column_name)) + { + if (const QTableWidgetItem* serial_item = item(row, gui::column_serial); serial_item && serial_item->text() == serial) + { + title_item->setIcon(game_list_base::GetCustomConfigIcon(game)); + } + } + } +} + +void game_list_table::populate( + const std::vector& game_data, + const QMap& notes_map, + const QMap& title_map, + const std::string& selected_item_id, + bool play_hover_movies) +{ + clear_list(); + + setRowCount(::narrow(game_data.size())); + + // Default locale. Uses current Qt application language. + const QLocale locale{}; + const Localized localized; + + const QString game_icon_path = play_hover_movies ? QString::fromStdString(fs::get_config_dir() + "/Icons/game_icons/") : ""; + const std::string dev_flash = g_cfg_vfs.get_dev_flash(); + + int row = 0; + int index = -1; + int selected_row = -1; + + for (const auto& game : game_data) + { + index++; + + const QString serial = QString::fromStdString(game->info.serial); + const QString title = title_map.value(serial, QString::fromStdString(game->info.name)); + const QString notes = notes_map.value(serial); + + // Icon + custom_table_widget_item* icon_item = new custom_table_widget_item; + game->item = icon_item; + + icon_item->set_icon_func([this, icon_item, game](int) + { + if (!icon_item || !game) + { + return; + } + + if (std::shared_ptr movie = icon_item->movie(); movie && icon_item->get_active()) + { + icon_item->setData(Qt::DecorationRole, movie->currentPixmap().scaled(m_icon_size, Qt::KeepAspectRatio)); + } + else + { + std::lock_guard lock(icon_item->pixmap_mutex); + + icon_item->setData(Qt::DecorationRole, game->pxmap); + + if (!game->has_hover_gif) + { + game->pxmap = {}; + } + + if (movie) + { + movie->stop(); + } + } + }); + + icon_item->set_size_calc_func([this, game, cancel = icon_item->size_on_disk_loading_aborted(), dev_flash]() + { + if (game && game->info.size_on_disk == umax && (!cancel || !cancel->load())) + { + if (game->info.path.starts_with(dev_flash)) + { + // Do not report size of apps inside /dev_flash (it does not make sense to do so) + game->info.size_on_disk = 0; + } + else + { + game->info.size_on_disk = fs::get_dir_size(game->info.path, 1, cancel.get()); + } + + if (!cancel || !cancel->load()) + { + Q_EMIT size_on_disk_ready(game); + return; + } + } + }); + + if (play_hover_movies && game->has_hover_gif) + { + icon_item->init_movie(game_icon_path % serial % "/hover.gif"); + } + + icon_item->setData(Qt::UserRole, index, true); + icon_item->setData(gui::custom_roles::game_role, QVariant::fromValue(game)); + + // Title + custom_table_widget_item* title_item = new custom_table_widget_item(title); + title_item->setIcon(game_list_base::GetCustomConfigIcon(game)); + + // Serial + custom_table_widget_item* serial_item = new custom_table_widget_item(game->info.serial); + + if (!notes.isEmpty()) + { + const QString tool_tip = tr("%0 [%1]\n\nNotes:\n%2").arg(title).arg(serial).arg(notes); + title_item->setToolTip(tool_tip); + serial_item->setToolTip(tool_tip); + } + + // Move Support (http://www.psdevwiki.com/ps3/PARAM.SFO#ATTRIBUTE) + const bool supports_move = game->info.attr & 0x800000; + + // Compatibility + custom_table_widget_item* compat_item = new custom_table_widget_item; + compat_item->setText(game->compat.text % (game->compat.date.isEmpty() ? QStringLiteral("") : " (" % game->compat.date % ")")); + compat_item->setData(Qt::UserRole, game->compat.index, true); + compat_item->setToolTip(game->compat.tooltip); + if (!game->compat.color.isEmpty()) + { + compat_item->setData(Qt::DecorationRole, gui::utils::circle_pixmap(game->compat.color, devicePixelRatioF() * 2)); + } + + // Version + QString app_version = QString::fromStdString(game_list::GetGameVersion(game)); + + if (game->info.bootable && !game->compat.latest_version.isEmpty()) + { + f64 top_ver = 0.0, app_ver = 0.0; + const bool unknown = app_version == localized.category.unknown; + const bool ok_app = !unknown && try_to_float(&app_ver, app_version.toStdString(), ::std::numeric_limits::min(), ::std::numeric_limits::max()); + const bool ok_top = !unknown && try_to_float(&top_ver, game->compat.latest_version.toStdString(), ::std::numeric_limits::min(), ::std::numeric_limits::max()); + + // If the app is bootable and the compat database contains info about the latest patch version: + // add a hint for available software updates if the app version is unknown or lower than the latest version. + if (unknown || (ok_top && ok_app && top_ver > app_ver)) + { + app_version = tr("%0 (Update available: %1)").arg(app_version, game->compat.latest_version); + } + } + + // Playtimes + const quint64 elapsed_ms = m_persistent_settings->GetPlaytime(serial); + + // Last played (support outdated values) + QDateTime last_played; + const QString last_played_str = m_persistent_settings->GetLastPlayed(serial); + + if (!last_played_str.isEmpty()) + { + last_played = QDateTime::fromString(last_played_str, gui::persistent::last_played_date_format); + + if (!last_played.isValid()) + { + last_played = QDateTime::fromString(last_played_str, gui::persistent::last_played_date_format_old); + } + } + + const u64 game_size = game->info.size_on_disk; + + setItem(row, gui::column_icon, icon_item); + setItem(row, gui::column_name, title_item); + setItem(row, gui::column_serial, serial_item); + setItem(row, gui::column_firmware, new custom_table_widget_item(game->info.fw)); + setItem(row, gui::column_version, new custom_table_widget_item(app_version)); + setItem(row, gui::column_category, new custom_table_widget_item(game->localized_category)); + setItem(row, gui::column_path, new custom_table_widget_item(game->info.path)); + setItem(row, gui::column_move, new custom_table_widget_item((supports_move ? tr("Supported") : tr("Not Supported")).toStdString(), Qt::UserRole, !supports_move)); + setItem(row, gui::column_resolution, new custom_table_widget_item(Localized::GetStringFromU32(game->info.resolution, localized.resolution.mode, true))); + setItem(row, gui::column_sound, new custom_table_widget_item(Localized::GetStringFromU32(game->info.sound_format, localized.sound.format, true))); + setItem(row, gui::column_parental, new custom_table_widget_item(Localized::GetStringFromU32(game->info.parental_lvl, localized.parental.level), Qt::UserRole, game->info.parental_lvl)); + setItem(row, gui::column_last_play, new custom_table_widget_item(locale.toString(last_played, last_played >= QDateTime::currentDateTime().addDays(-7) ? gui::persistent::last_played_date_with_time_of_day_format : gui::persistent::last_played_date_format_new), Qt::UserRole, last_played)); + setItem(row, gui::column_playtime, new custom_table_widget_item(elapsed_ms == 0 ? tr("Never played") : localized.GetVerboseTimeByMs(elapsed_ms), Qt::UserRole, elapsed_ms)); + setItem(row, gui::column_compat, compat_item); + setItem(row, gui::column_dir_size, new custom_table_widget_item(game_size != umax ? gui::utils::format_byte_size(game_size) : tr("Unknown"), Qt::UserRole, QVariant::fromValue(game_size))); + + if (selected_item_id == game->info.path + game->info.icon_path) + { + selected_row = row; + } + + row++; + } + + selectRow(selected_row); +} + +void game_list_table::repaint_icons(QList& game_data, const QColor& icon_color, const QSize& icon_size, qreal device_pixel_ratio) +{ + game_list_base::repaint_icons(game_data, icon_color, icon_size, device_pixel_ratio); + adjust_icon_column(); +} diff --git a/rpcs3/rpcs3qt/game_list_table.h b/rpcs3/rpcs3qt/game_list_table.h new file mode 100644 index 0000000000..e245bd11b7 --- /dev/null +++ b/rpcs3/rpcs3qt/game_list_table.h @@ -0,0 +1,45 @@ +#pragma once + +#include "game_list.h" + +class persistent_settings; +class game_list_frame; + +class game_list_table : public game_list +{ + Q_OBJECT + +public: + game_list_table(game_list_frame* frame, std::shared_ptr persistent_settings); + + /** Restores the initial layout of the table */ + void restore_layout(const QByteArray& state); + + /** Fix columns with width smaller than the minimal section size */ + void fix_narrow_columns(); + + /** Resizes the columns to their contents and adds a small spacing */ + void resize_columns_to_contents(int spacing = 20); + + void adjust_icon_column(); + + void sort(int game_count, int sort_column, Qt::SortOrder col_sort_order); + + void set_custom_config_icon(const game_info& game); + + void populate( + const std::vector& game_data, + const QMap& notes_map, + const QMap& title_map, + const std::string& selected_item_id, + bool play_hover_movies) override; + + void repaint_icons(QList& game_data, const QColor& icon_color, const QSize& icon_size, qreal device_pixel_ratio) override; + +Q_SIGNALS: + void size_on_disk_ready(const game_info& game); + +private: + game_list_frame* m_game_list_frame{}; + std::shared_ptr m_persistent_settings; +}; diff --git a/rpcs3/rpcs3qt/localized.cpp b/rpcs3/rpcs3qt/localized.cpp index f0f176c5f0..ab6f9c34cd 100644 --- a/rpcs3/rpcs3qt/localized.cpp +++ b/rpcs3/rpcs3qt/localized.cpp @@ -47,6 +47,36 @@ QString Localized::GetVerboseTimeByMs(quint64 elapsed_ms, bool show_days) const return str_seconds; } +std::string Localized::GetStringFromU32(const u32& key, const std::map& map, bool combined) +{ + QStringList string; + + if (combined) + { + for (const auto& item : map) + { + if (key & item.first) + { + string << item.second; + } + } + } + else + { + if (map.find(key) != map.end()) + { + string << ::at32(map, key); + } + } + + if (string.isEmpty()) + { + string << tr("Unknown"); + } + + return string.join(", ").toStdString(); +} + Localized::resolution::resolution() : mode({ { psf::resolution_flag::_480p, tr("480p") }, diff --git a/rpcs3/rpcs3qt/localized.h b/rpcs3/rpcs3qt/localized.h index 6245d586cd..cfd3b9e437 100644 --- a/rpcs3/rpcs3qt/localized.h +++ b/rpcs3/rpcs3qt/localized.h @@ -17,6 +17,7 @@ public: Localized() {} QString GetVerboseTimeByMs(quint64 elapsed_ms, bool show_days = false) const; + static std::string GetStringFromU32(const u32& key, const std::map& map, bool combined = false); const struct category // (see PARAM.SFO in psdevwiki.com) TODO: Disc Categories { diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 2e346fa868..f2909f5010 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -2430,7 +2430,7 @@ void main_window::CreateConnects() { if (game) { - games[game->info.serial].insert(game_list_frame::GetGameVersion(game)); + games[game->info.serial].insert(game_list::GetGameVersion(game)); } } } diff --git a/rpcs3/rpcs3qt/memory_string_searcher.cpp b/rpcs3/rpcs3qt/memory_string_searcher.cpp index 28cf5b6ff9..12bd10c9ed 100644 --- a/rpcs3/rpcs3qt/memory_string_searcher.cpp +++ b/rpcs3/rpcs3qt/memory_string_searcher.cpp @@ -8,11 +8,6 @@ #include "Utilities/Thread.h" #include "Utilities/StrUtil.h" -#include -#include -#include -#include -#include #include #include @@ -24,8 +19,6 @@ LOG_CHANNEL(gui_log, "GUI"); -constexpr auto qstr = QString::fromStdString; - template <> void fmt_class_string::format(std::string& out, u64 arg) { diff --git a/rpcs3/rpcs3qt/movie_item.cpp b/rpcs3/rpcs3qt/movie_item.cpp index 7c65b3781e..3cfb269709 100644 --- a/rpcs3/rpcs3qt/movie_item.cpp +++ b/rpcs3/rpcs3qt/movie_item.cpp @@ -1,152 +1,14 @@ #include "stdafx.h" #include "movie_item.h" -movie_item::movie_item() : QTableWidgetItem() +movie_item::movie_item() : QTableWidgetItem(), movie_item_base() { - init_pointers(); } -movie_item::movie_item(const QString& text, int type) : QTableWidgetItem(text, type) +movie_item::movie_item(const QString& text, int type) : QTableWidgetItem(text, type), movie_item_base() { - init_pointers(); } -movie_item::movie_item(const QIcon& icon, const QString& text, int type) : QTableWidgetItem(icon, text, type) +movie_item::movie_item(const QIcon& icon, const QString& text, int type) : QTableWidgetItem(icon, text, type), movie_item_base() { - init_pointers(); -} - -movie_item::~movie_item() -{ - if (m_movie) - { - m_movie->stop(); - } - - wait_for_icon_loading(true); - wait_for_size_on_disk_loading(true); -} - -void movie_item::init_pointers() -{ - m_icon_loading_aborted.reset(new atomic_t(false)); - m_size_on_disk_loading_aborted.reset(new atomic_t(false)); -} - -void movie_item::set_active(bool active) -{ - if (!std::exchange(m_active, active) && active && m_movie) - { - m_movie->jumpToFrame(1); - m_movie->start(); - } -} - -void movie_item::init_movie(const QString& path) -{ - if (path.isEmpty() || !m_icon_callback) return; - - m_movie.reset(new QMovie(path)); - - if (!m_movie->isValid()) - { - m_movie.reset(); - return; - } - - QObject::connect(m_movie.get(), &QMovie::frameChanged, m_movie.get(), m_icon_callback); -} - -void movie_item::call_icon_func() const -{ - if (m_icon_callback) - { - m_icon_callback(0); - } -} - -void movie_item::set_icon_func(const icon_callback_t& func) -{ - m_icon_callback = func; - call_icon_func(); -} - -void movie_item::call_icon_load_func(int index) -{ - if (!m_icon_load_callback || m_icon_loading || m_icon_loading_aborted->load()) - { - return; - } - - wait_for_icon_loading(true); - - *m_icon_loading_aborted = false; - m_icon_loading = true; - m_icon_load_thread.reset(QThread::create([this, index]() - { - if (m_icon_load_callback) - { - m_icon_load_callback(index); - } - })); - m_icon_load_thread->start(); -} - -void movie_item::set_icon_load_func(const icon_load_callback_t& func) -{ - wait_for_icon_loading(true); - - m_icon_loading = false; - m_icon_load_callback = func; - *m_icon_loading_aborted = false; -} - -void movie_item::call_size_calc_func() -{ - if (!m_size_calc_callback || m_size_on_disk_loading || m_size_on_disk_loading_aborted->load()) - { - return; - } - - wait_for_size_on_disk_loading(true); - - *m_size_on_disk_loading_aborted = false; - m_size_on_disk_loading = true; - m_size_calc_thread.reset(QThread::create([this]() - { - if (m_size_calc_callback) - { - m_size_calc_callback(); - } - })); - m_size_calc_thread->start(); -} - -void movie_item::set_size_calc_func(const size_calc_callback_t& func) -{ - m_size_on_disk_loading = false; - m_size_calc_callback = func; - *m_size_on_disk_loading_aborted = false; -} - -void movie_item::wait_for_icon_loading(bool abort) -{ - *m_icon_loading_aborted = abort; - - if (m_icon_load_thread && m_icon_load_thread->isRunning()) - { - m_icon_load_thread->wait(); - m_icon_load_thread.reset(); - } -} - -void movie_item::wait_for_size_on_disk_loading(bool abort) -{ - *m_size_on_disk_loading_aborted = abort; - - if (m_size_calc_thread && m_size_calc_thread->isRunning()) - { - m_size_calc_thread->wait(); - m_size_calc_thread.reset(); - } } diff --git a/rpcs3/rpcs3qt/movie_item.h b/rpcs3/rpcs3qt/movie_item.h index ac07560167..348867a63e 100644 --- a/rpcs3/rpcs3qt/movie_item.h +++ b/rpcs3/rpcs3qt/movie_item.h @@ -1,89 +1,13 @@ #pragma once -#include "util/atomic.hpp" -#include "Utilities/mutex.h" +#include "movie_item_base.h" #include -#include -#include -#include -#include -#include - -using icon_callback_t = std::function; -using icon_load_callback_t = std::function; -using size_calc_callback_t = std::function; - -class movie_item : public QTableWidgetItem +class movie_item : public QTableWidgetItem, public movie_item_base { public: movie_item(); movie_item(const QString& text, int type = Type); movie_item(const QIcon& icon, const QString& text, int type = Type); - ~movie_item(); - - void init_pointers(); - - void set_active(bool active); - - [[nodiscard]] bool get_active() const - { - return m_active; - } - - [[nodiscard]] std::shared_ptr movie() const - { - return m_movie; - } - - void init_movie(const QString& path); - - void call_icon_func() const; - void set_icon_func(const icon_callback_t& func); - - void call_icon_load_func(int index); - void set_icon_load_func(const icon_load_callback_t& func); - - void call_size_calc_func(); - void set_size_calc_func(const size_calc_callback_t& func); - - void wait_for_icon_loading(bool abort); - void wait_for_size_on_disk_loading(bool abort); - - bool icon_loading() const - { - return m_icon_loading; - } - - bool size_on_disk_loading() const - { - return m_size_on_disk_loading; - } - - [[nodiscard]] std::shared_ptr> icon_loading_aborted() const - { - return m_icon_loading_aborted; - } - - [[nodiscard]] std::shared_ptr> size_on_disk_loading_aborted() const - { - return m_size_on_disk_loading_aborted; - } - - shared_mutex pixmap_mutex; - -private: - std::shared_ptr m_movie; - std::unique_ptr m_icon_load_thread; - std::unique_ptr m_size_calc_thread; - bool m_active = false; - atomic_t m_size_on_disk_loading = false; - atomic_t m_icon_loading = false; - size_calc_callback_t m_size_calc_callback = nullptr; - icon_load_callback_t m_icon_load_callback = nullptr; - icon_callback_t m_icon_callback = nullptr; - - std::shared_ptr> m_icon_loading_aborted; - std::shared_ptr> m_size_on_disk_loading_aborted; }; diff --git a/rpcs3/rpcs3qt/movie_item_base.cpp b/rpcs3/rpcs3qt/movie_item_base.cpp new file mode 100644 index 0000000000..48cd3f5af3 --- /dev/null +++ b/rpcs3/rpcs3qt/movie_item_base.cpp @@ -0,0 +1,141 @@ +#include "stdafx.h" +#include "movie_item_base.h" + +movie_item_base::movie_item_base() +{ + init_pointers(); +} + +movie_item_base::~movie_item_base() +{ + if (m_movie) + { + m_movie->stop(); + } + + wait_for_icon_loading(true); + wait_for_size_on_disk_loading(true); +} + +void movie_item_base::init_pointers() +{ + m_icon_loading_aborted.reset(new atomic_t(false)); + m_size_on_disk_loading_aborted.reset(new atomic_t(false)); +} + +void movie_item_base::set_active(bool active) +{ + if (!std::exchange(m_active, active) && active && m_movie) + { + m_movie->jumpToFrame(1); + m_movie->start(); + } +} + +void movie_item_base::init_movie(const QString& path) +{ + if (path.isEmpty() || !m_icon_callback) return; + + m_movie.reset(new QMovie(path)); + + if (!m_movie->isValid()) + { + m_movie.reset(); + return; + } + + QObject::connect(m_movie.get(), &QMovie::frameChanged, m_movie.get(), m_icon_callback); +} + +void movie_item_base::call_icon_func() const +{ + if (m_icon_callback) + { + m_icon_callback(0); + } +} + +void movie_item_base::set_icon_func(const icon_callback_t& func) +{ + m_icon_callback = func; +} + +void movie_item_base::call_icon_load_func(int index) +{ + if (!m_icon_load_callback || m_icon_loading || m_icon_loading_aborted->load()) + { + return; + } + + wait_for_icon_loading(true); + + *m_icon_loading_aborted = false; + m_icon_loading = true; + m_icon_load_thread.reset(QThread::create([this, index]() + { + if (m_icon_load_callback) + { + m_icon_load_callback(index); + } + })); + m_icon_load_thread->start(); +} + +void movie_item_base::set_icon_load_func(const icon_load_callback_t& func) +{ + wait_for_icon_loading(true); + + m_icon_loading = false; + m_icon_load_callback = func; + *m_icon_loading_aborted = false; +} + +void movie_item_base::call_size_calc_func() +{ + if (!m_size_calc_callback || m_size_on_disk_loading || m_size_on_disk_loading_aborted->load()) + { + return; + } + + wait_for_size_on_disk_loading(true); + + *m_size_on_disk_loading_aborted = false; + m_size_on_disk_loading = true; + m_size_calc_thread.reset(QThread::create([this]() + { + if (m_size_calc_callback) + { + m_size_calc_callback(); + } + })); + m_size_calc_thread->start(); +} + +void movie_item_base::set_size_calc_func(const size_calc_callback_t& func) +{ + m_size_on_disk_loading = false; + m_size_calc_callback = func; + *m_size_on_disk_loading_aborted = false; +} + +void movie_item_base::wait_for_icon_loading(bool abort) +{ + *m_icon_loading_aborted = abort; + + if (m_icon_load_thread && m_icon_load_thread->isRunning()) + { + m_icon_load_thread->wait(); + m_icon_load_thread.reset(); + } +} + +void movie_item_base::wait_for_size_on_disk_loading(bool abort) +{ + *m_size_on_disk_loading_aborted = abort; + + if (m_size_calc_thread && m_size_calc_thread->isRunning()) + { + m_size_calc_thread->wait(); + m_size_calc_thread.reset(); + } +} diff --git a/rpcs3/rpcs3qt/movie_item_base.h b/rpcs3/rpcs3qt/movie_item_base.h new file mode 100644 index 0000000000..ae6071eadc --- /dev/null +++ b/rpcs3/rpcs3qt/movie_item_base.h @@ -0,0 +1,88 @@ +#pragma once + +#include "movie_item_base.h" +#include "util/atomic.hpp" +#include "Utilities/mutex.h" + +#include +#include + +#include +#include + +using icon_callback_t = std::function; +using icon_load_callback_t = std::function; +using size_calc_callback_t = std::function; + +class movie_item_base +{ +public: + movie_item_base(); + virtual ~movie_item_base(); + + void init_pointers(); + + void set_active(bool active); + + [[nodiscard]] bool get_active() const + { + return m_active; + } + + [[nodiscard]] std::shared_ptr movie() const + { + return m_movie; + } + + void init_movie(const QString& path); + + void call_icon_func() const; + void set_icon_func(const icon_callback_t& func); + + void call_icon_load_func(int index); + void set_icon_load_func(const icon_load_callback_t& func); + + void call_size_calc_func(); + void set_size_calc_func(const size_calc_callback_t& func); + + void wait_for_icon_loading(bool abort); + void wait_for_size_on_disk_loading(bool abort); + + bool icon_loading() const + { + return m_icon_loading; + } + + bool size_on_disk_loading() const + { + return m_size_on_disk_loading; + } + + [[nodiscard]] std::shared_ptr> icon_loading_aborted() const + { + return m_icon_loading_aborted; + } + + [[nodiscard]] std::shared_ptr> size_on_disk_loading_aborted() const + { + return m_size_on_disk_loading_aborted; + } + + shared_mutex pixmap_mutex; + +protected: + std::shared_ptr m_movie; + +private: + std::unique_ptr m_icon_load_thread; + std::unique_ptr m_size_calc_thread; + bool m_active = false; + atomic_t m_size_on_disk_loading = false; + atomic_t m_icon_loading = false; + size_calc_callback_t m_size_calc_callback = nullptr; + icon_load_callback_t m_icon_load_callback = nullptr; + icon_callback_t m_icon_callback = nullptr; + + std::shared_ptr> m_icon_loading_aborted; + std::shared_ptr> m_size_on_disk_loading_aborted; +}; diff --git a/rpcs3/rpcs3qt/qt_utils.cpp b/rpcs3/rpcs3qt/qt_utils.cpp index 23719eff58..a179b28cb0 100644 --- a/rpcs3/rpcs3qt/qt_utils.cpp +++ b/rpcs3/rpcs3qt/qt_utils.cpp @@ -198,15 +198,15 @@ namespace gui return l.sizeHint().width(); } - QImage get_centered_image(const QString& path, const QSize& icon_size, int offset_x, int offset_y, qreal device_pixel_ratio) + QPixmap get_centered_pixmap(QPixmap pixmap, const QSize& icon_size, int offset_x, int offset_y, qreal device_pixel_ratio, Qt::TransformationMode mode) { // Create empty canvas for expanded image - QImage exp_img(icon_size, QImage::Format_ARGB32); + QPixmap exp_img(icon_size); exp_img.setDevicePixelRatio(device_pixel_ratio); exp_img.fill(Qt::transparent); // Load scaled pixmap - const QPixmap pixmap = QPixmap(path).scaled(icon_size, Qt::KeepAspectRatio, Qt::SmoothTransformation); + pixmap = pixmap.scaled(icon_size, Qt::KeepAspectRatio, mode); // Define offset for raw image placement QPoint offset(offset_x + icon_size.width() / 2 - pixmap.width() / 2, @@ -221,9 +221,9 @@ namespace gui return exp_img; } - QPixmap get_centered_pixmap(const QString& path, const QSize& icon_size, int offset_x, int offset_y, qreal device_pixel_ratio) + QPixmap get_centered_pixmap(const QString& path, const QSize& icon_size, int offset_x, int offset_y, qreal device_pixel_ratio, Qt::TransformationMode mode) { - return QPixmap::fromImage(get_centered_image(path, icon_size, offset_x, offset_y, device_pixel_ratio)); + return get_centered_pixmap(QPixmap(path), icon_size, offset_x, offset_y, device_pixel_ratio, mode); } QImage get_opaque_image_area(const QString& path) diff --git a/rpcs3/rpcs3qt/qt_utils.h b/rpcs3/rpcs3qt/qt_utils.h index 9a15f68ddd..32312b34ae 100644 --- a/rpcs3/rpcs3qt/qt_utils.h +++ b/rpcs3/rpcs3qt/qt_utils.h @@ -82,11 +82,11 @@ namespace gui qobj.setFont(font); } - // Returns a scaled, centered QImage - QImage get_centered_image(const QString& path, const QSize& icon_size, int offset_x, int offset_y, qreal device_pixel_ratio); + // Returns a scaled, centered QPixmap + QPixmap get_centered_pixmap(QPixmap pixmap, const QSize& icon_size, int offset_x, int offset_y, qreal device_pixel_ratio, Qt::TransformationMode mode); // Returns a scaled, centered QPixmap - QPixmap get_centered_pixmap(const QString& path, const QSize& icon_size, int offset_x, int offset_y, qreal device_pixel_ratio); + QPixmap get_centered_pixmap(const QString& path, const QSize& icon_size, int offset_x, int offset_y, qreal device_pixel_ratio, Qt::TransformationMode mode); // Returns the part of the image loaded from path that is inside the bounding box of its opaque areas QImage get_opaque_image_area(const QString& path); diff --git a/rpcs3/rpcs3qt/render_creator.cpp b/rpcs3/rpcs3qt/render_creator.cpp index ba6eff83aa..c939c729fb 100644 --- a/rpcs3/rpcs3qt/render_creator.cpp +++ b/rpcs3/rpcs3qt/render_creator.cpp @@ -16,8 +16,6 @@ LOG_CHANNEL(cfg_log, "CFG"); -constexpr auto qstr = QString::fromStdString; - render_creator::render_creator(QObject *parent) : QObject(parent) { #if defined(HAVE_VULKAN) @@ -50,9 +48,9 @@ render_creator::render_creator(QObject *parent) : QObject(parent) if (!work_done) // The spawning thread gave up, do not attempt to modify vulkan_adapters { - for (auto& gpu : gpus) + for (const auto& gpu : gpus) { - adapters->append(qstr(gpu.get_name())); + adapters->append(QString::fromStdString(gpu.get_name())); } } } diff --git a/rpcs3/rpcs3qt/screenshot_item.cpp b/rpcs3/rpcs3qt/screenshot_item.cpp new file mode 100644 index 0000000000..d0916f7114 --- /dev/null +++ b/rpcs3/rpcs3qt/screenshot_item.cpp @@ -0,0 +1,32 @@ +#include "screenshot_item.h" +#include "qt_utils.h" + +#include + +screenshot_item::screenshot_item(QWidget* parent) + : flow_widget_item(parent) +{ + cb_on_first_visibility = [this]() + { + m_thread.reset(QThread::create([this]() + { + const QPixmap pixmap = gui::utils::get_centered_pixmap(icon_path, icon_size, 0, 0, 1.0, Qt::SmoothTransformation); + Q_EMIT signal_icon_update(pixmap); + })); + m_thread->start(); + }; + + label = new QLabel(this); + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(label); + setLayout(layout); +} + +screenshot_item::~screenshot_item() +{ + if (m_thread && m_thread->isRunning()) + { + m_thread->wait(); + } +} diff --git a/rpcs3/rpcs3qt/screenshot_item.h b/rpcs3/rpcs3qt/screenshot_item.h new file mode 100644 index 0000000000..36f0232b1f --- /dev/null +++ b/rpcs3/rpcs3qt/screenshot_item.h @@ -0,0 +1,24 @@ +#pragma once + +#include "flow_widget_item.h" +#include +#include + +class screenshot_item : public flow_widget_item +{ + Q_OBJECT + +public: + screenshot_item(QWidget* parent); + virtual ~screenshot_item(); + + QString icon_path; + QSize icon_size; + QLabel* label{}; + +private: + std::unique_ptr m_thread; + +Q_SIGNALS: + void signal_icon_update(const QPixmap& pixmap); +}; diff --git a/rpcs3/rpcs3qt/screenshot_manager_dialog.cpp b/rpcs3/rpcs3qt/screenshot_manager_dialog.cpp index f25aee4494..5564bd4bf4 100644 --- a/rpcs3/rpcs3qt/screenshot_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/screenshot_manager_dialog.cpp @@ -1,6 +1,7 @@ #include "stdafx.h" #include "screenshot_manager_dialog.h" #include "screenshot_preview.h" +#include "screenshot_item.h" #include "flow_widget.h" #include "qt_utils.h" #include "Utilities/File.h" @@ -23,7 +24,7 @@ screenshot_manager_dialog::screenshot_manager_dialog(QWidget* parent) : QDialog( m_icon_size = QSize(160, 90); m_flow_widget = new flow_widget(this); - m_flow_widget->setObjectName("m_flow_widget"); + m_flow_widget->setObjectName("flow_widget"); m_placeholder = QPixmap(m_icon_size); m_placeholder.fill(Qt::gray); @@ -137,31 +138,3 @@ bool screenshot_manager_dialog::eventFilter(QObject* watched, QEvent* event) return false; } - -screenshot_item::screenshot_item(QWidget* parent) - : flow_widget_item(parent) -{ - cb_on_first_visibility = [this]() - { - m_thread.reset(QThread::create([this]() - { - const QPixmap pixmap = gui::utils::get_centered_pixmap(icon_path, icon_size, 0, 0, 1.0); - Q_EMIT signal_icon_update(pixmap); - })); - m_thread->start(); - }; - - label = new QLabel(this); - QVBoxLayout* layout = new QVBoxLayout(this); - layout->setContentsMargins(0, 0, 0, 0); - layout->addWidget(label); - setLayout(layout); -} - -screenshot_item::~screenshot_item() -{ - if (m_thread && m_thread->isRunning()) - { - m_thread->wait(); - } -} diff --git a/rpcs3/rpcs3qt/screenshot_manager_dialog.h b/rpcs3/rpcs3qt/screenshot_manager_dialog.h index 46f908d4dc..ab4d7c13a8 100644 --- a/rpcs3/rpcs3qt/screenshot_manager_dialog.h +++ b/rpcs3/rpcs3qt/screenshot_manager_dialog.h @@ -6,9 +6,7 @@ #include #include #include -#include #include -#include #include class screenshot_manager_dialog : public QDialog @@ -46,22 +44,3 @@ private: QSize m_icon_size; QPixmap m_placeholder; }; - -class screenshot_item : public flow_widget_item -{ - Q_OBJECT - -public: - screenshot_item(QWidget* parent); - virtual ~screenshot_item(); - - QString icon_path; - QSize icon_size; - QLabel* label{}; - -private: - std::unique_ptr m_thread; - -Q_SIGNALS: - void signal_icon_update(const QPixmap& pixmap); -}; diff --git a/rpcs3/rpcs3qt/stylesheets.h b/rpcs3/rpcs3qt/stylesheets.h index 9121d03609..15c32b7473 100644 --- a/rpcs3/rpcs3qt/stylesheets.h +++ b/rpcs3/rpcs3qt/stylesheets.h @@ -29,6 +29,20 @@ namespace gui // game list icon color "QLabel#gamelist_icon_background_color { color: rgba(240, 240, 240, 255); }" + // game grid + "#game_list_grid #flow_widget_content { background: #fff; }" + "#game_list_grid_item { background: #fff; }" + "#game_list_grid_item[selected=\"true\"] { background: #148aff; }" + "#game_list_grid_item:focus { background: #148aff; }" + "#game_list_grid_item:hover { background: #94c9ff; }" + "#game_list_grid_item:hover:focus { background: #007fff; }" + "#game_list_grid_item #game_list_grid_item_title_label { color: rgba(51, 51, 51, 255); font-weight: 600; font-size: 8pt; font-family: Lucida Grande; border: 0em solid white; }" + + // game grid hover and focus: we need to handle properties differently when using descendants + "#game_list_grid_item[selected=\"true\"] #game_list_grid_item_title_label { color: #fff; }" + "#game_list_grid_item[hover=\"true\"] #game_list_grid_item_title_label { color: #fff; }" + "#game_list_grid_item[focus=\"true\"] #game_list_grid_item_title_label { color: #fff; }" + // save manager icon color "QLabel#save_manager_icon_background_color { color: rgba(240, 240, 240, 255); }" @@ -37,11 +51,8 @@ namespace gui // tables "QTableWidget { alternate-background-color: #f2f2f2; background-color: #fff; border: none; }" - "QTableWidget#game_grid { alternate-background-color: #f2f2f2; background-color: #fff; font-weight: 600; font-size: 8pt; font-family: Lucida Grande; color: rgba(51, 51, 51, 255); border: 0em solid white; }" "QTableView::item { border-left: 0.063em solid white; border-right: 0.063em solid white; padding-left:0.313em; }" "QTableView::item:selected { background-color: #148aff; color: #fff; }" - "QTableView#game_grid::item:hover:!selected { background-color: #94c9ff; color: #fff; }" - "QTableView#game_grid::item:hover:selected { background-color: #007fff; color: #fff; }" // table headers "QHeaderView::section { padding-left: .5em; padding-right: .5em; padding-top: .4em; padding-bottom: -.1em; border: 0.063em solid #ffffff; }"