mirror of https://github.com/xemu-project/xemu.git
ui: Redesign user interface
Introduces a new user interface that looks much nicer, is easier to navigate with controllers, provides more context to users, and is scalable. Some additional features are included. * Adds 'popup menu' with actions that can be used easily from controller * Adds 'main menu', unifying other configuration dialogs * Adds port-forwarding user interface * Adds screenshot feature * Adds volume control feature * Adds gamepad auto-bind option * Adds vsync configuration option * Adds auto UI scaling * Adds preferred window size selection * Adds AV pack selection * Exposes some existing config items in GUI
This commit is contained in:
parent
306891b98c
commit
9c06980275
|
@ -68,6 +68,7 @@ IncludeCategories:
|
||||||
IncludeIsMainRegex: '$'
|
IncludeIsMainRegex: '$'
|
||||||
IndentCaseLabels: false
|
IndentCaseLabels: false
|
||||||
IndentWidth: 4
|
IndentWidth: 4
|
||||||
|
AccessModifierOffset: -4
|
||||||
IndentWrappedFunctionNames: false
|
IndentWrappedFunctionNames: false
|
||||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||||
MacroBlockBegin: '.*_BEGIN$' # only PREC_BEGIN ?
|
MacroBlockBegin: '.*_BEGIN$' # only PREC_BEGIN ?
|
||||||
|
|
|
@ -64,12 +64,12 @@
|
||||||
[submodule "roms/vbootrom"]
|
[submodule "roms/vbootrom"]
|
||||||
path = roms/vbootrom
|
path = roms/vbootrom
|
||||||
url = https://gitlab.com/qemu-project/vbootrom.git
|
url = https://gitlab.com/qemu-project/vbootrom.git
|
||||||
[submodule "ui/imgui"]
|
[submodule "ui/thirdparty/imgui"]
|
||||||
path = ui/imgui
|
path = ui/thirdparty/imgui
|
||||||
url = https://github.com/ocornut/imgui.git
|
url = https://github.com/ocornut/imgui.git
|
||||||
ignore = untracked
|
ignore = untracked
|
||||||
[submodule "ui/implot"]
|
[submodule "ui/thirdparty/implot"]
|
||||||
path = ui/implot
|
path = ui/thirdparty/implot
|
||||||
url = https://github.com/epezent/implot.git
|
url = https://github.com/epezent/implot.git
|
||||||
ignore = untracked
|
ignore = untracked
|
||||||
[submodule "hw/xbox/nv2a/xxHash"]
|
[submodule "hw/xbox/nv2a/xxHash"]
|
||||||
|
|
5
build.sh
5
build.sh
|
@ -272,11 +272,6 @@ set -x # Print commands from now on
|
||||||
${opts} \
|
${opts} \
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
# Force imgui update now to work around annoying make issue
|
|
||||||
if ! test -f "${project_source_dir}/ui/imgui/imgui.cpp"; then
|
|
||||||
./scripts/git-submodule.sh update ui/imgui
|
|
||||||
fi
|
|
||||||
|
|
||||||
time make -j"${job_count}" ${target} 2>&1 | tee build.log
|
time make -j"${job_count}" ${target} 2>&1 | tee build.log
|
||||||
|
|
||||||
"${postbuild}" # call post build functions
|
"${postbuild}" # call post build functions
|
||||||
|
|
|
@ -6,8 +6,10 @@ general:
|
||||||
check:
|
check:
|
||||||
type: bool
|
type: bool
|
||||||
default: true
|
default: true
|
||||||
misc:
|
screenshot_dir: string
|
||||||
skip_boot_anim: bool
|
skip_boot_anim: bool
|
||||||
|
# throttle_io: bool
|
||||||
|
last_viewed_menu_index: integer
|
||||||
user_token: string
|
user_token: string
|
||||||
|
|
||||||
input:
|
input:
|
||||||
|
@ -16,6 +18,11 @@ input:
|
||||||
port2: string
|
port2: string
|
||||||
port3: string
|
port3: string
|
||||||
port4: string
|
port4: string
|
||||||
|
gamecontrollerdb_path: string
|
||||||
|
auto_bind:
|
||||||
|
type: bool
|
||||||
|
default: true
|
||||||
|
background_input_capture: bool
|
||||||
|
|
||||||
display:
|
display:
|
||||||
quality:
|
quality:
|
||||||
|
@ -23,9 +30,27 @@ display:
|
||||||
type: integer
|
type: integer
|
||||||
default: 1
|
default: 1
|
||||||
window:
|
window:
|
||||||
last_width: integer
|
fullscreen_on_startup: bool
|
||||||
last_height: integer
|
startup_size:
|
||||||
|
type: enum
|
||||||
|
values: [last_used, 640x480, 1280x720, 1280x800, 1280x960, 1920x1080, 2560x1440, 2560x1600, 2560x1920, 3840x2160]
|
||||||
|
default: 1280x960
|
||||||
|
last_width:
|
||||||
|
type: integer
|
||||||
|
default: 640
|
||||||
|
last_height:
|
||||||
|
type: integer
|
||||||
|
default: 480
|
||||||
|
vsync:
|
||||||
|
type: bool
|
||||||
|
default: true
|
||||||
ui:
|
ui:
|
||||||
|
show_menubar:
|
||||||
|
type: bool
|
||||||
|
default: true
|
||||||
|
use_animations:
|
||||||
|
type: bool
|
||||||
|
default: true
|
||||||
fit:
|
fit:
|
||||||
type: enum
|
type: enum
|
||||||
values: [center, scale, scale_16_9, scale_4_3, stretch]
|
values: [center, scale, scale_16_9, scale_4_3, stretch]
|
||||||
|
@ -33,9 +58,15 @@ display:
|
||||||
scale:
|
scale:
|
||||||
type: integer
|
type: integer
|
||||||
default: 1
|
default: 1
|
||||||
|
auto_scale:
|
||||||
|
type: bool
|
||||||
|
default: true
|
||||||
|
|
||||||
audio:
|
audio:
|
||||||
use_dsp: bool
|
use_dsp: bool
|
||||||
|
volume_limit:
|
||||||
|
type: number
|
||||||
|
default: 1
|
||||||
|
|
||||||
net:
|
net:
|
||||||
enable: bool
|
enable: bool
|
||||||
|
@ -52,12 +83,26 @@ net:
|
||||||
remote_addr:
|
remote_addr:
|
||||||
type: string
|
type: string
|
||||||
default: 1.2.3.4:9368
|
default: 1.2.3.4:9368
|
||||||
|
nat:
|
||||||
|
forward_ports:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
host: integer
|
||||||
|
guest: integer
|
||||||
|
protocol:
|
||||||
|
type: enum
|
||||||
|
values: [tcp, udp]
|
||||||
|
default: tcp
|
||||||
|
|
||||||
sys:
|
sys:
|
||||||
mem_limit:
|
mem_limit:
|
||||||
type: enum
|
type: enum
|
||||||
values: ['64', '128']
|
values: ['64', '128']
|
||||||
default: '64'
|
default: '64'
|
||||||
|
avpack:
|
||||||
|
type: enum
|
||||||
|
values: [scart, hdtv, vga, rfu, svideo, composite, none]
|
||||||
|
default: hdtv
|
||||||
files:
|
files:
|
||||||
bootrom_path: string
|
bootrom_path: string
|
||||||
flashrom_path: string
|
flashrom_path: string
|
||||||
|
@ -69,3 +114,6 @@ perf:
|
||||||
hard_fpu:
|
hard_fpu:
|
||||||
type: bool
|
type: bool
|
||||||
default: true
|
default: true
|
||||||
|
# cache_shaders:
|
||||||
|
# type: bool
|
||||||
|
# default: true
|
||||||
|
|
|
@ -261,7 +261,7 @@ else
|
||||||
git_submodules_action="ignore"
|
git_submodules_action="ignore"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
git_submodules="ui/keycodemapdb ui/imgui ui/implot util/xxHash"
|
git_submodules="ui/keycodemapdb ui/thirdparty/imgui ui/thirdparty/implot util/xxHash tomlplusplus genconfig"
|
||||||
git="git"
|
git="git"
|
||||||
|
|
||||||
# Don't accept a target_list environment variable.
|
# Don't accept a target_list environment variable.
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,156 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="76.111221mm"
|
||||||
|
height="9.6190205mm"
|
||||||
|
viewBox="0 0 76.111221 9.6190205"
|
||||||
|
version="1.1"
|
||||||
|
id="svg986"
|
||||||
|
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
|
||||||
|
sodipodi:docname="abxy.svg">
|
||||||
|
<defs
|
||||||
|
id="defs980" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="1.979899"
|
||||||
|
inkscape:cx="276.28204"
|
||||||
|
inkscape:cy="77.212294"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:current-layer="g1622"
|
||||||
|
showgrid="false"
|
||||||
|
fit-margin-top="0"
|
||||||
|
fit-margin-left="0"
|
||||||
|
fit-margin-right="0"
|
||||||
|
fit-margin-bottom="0"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1043"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1" />
|
||||||
|
<metadata
|
||||||
|
id="metadata983">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-48.12296,-137.22026)"
|
||||||
|
style="display:none">
|
||||||
|
<circle
|
||||||
|
style="opacity:0.9;fill:#1a1a1a;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
|
||||||
|
id="path1694"
|
||||||
|
cx="63.995201"
|
||||||
|
cy="141.96429"
|
||||||
|
r="4.6772165" />
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;line-height:1.25;font-family:'Roboto Condensed';-inkscape-font-specification:'Roboto Condensed, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||||
|
x="62.234642"
|
||||||
|
y="144.12724"
|
||||||
|
id="text1698"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan1696"
|
||||||
|
x="62.234642"
|
||||||
|
y="144.12724"
|
||||||
|
style="stroke-width:0.26458332">A</tspan></text>
|
||||||
|
<circle
|
||||||
|
r="4.6772165"
|
||||||
|
cy="141.96429"
|
||||||
|
cx="79.870201"
|
||||||
|
id="circle1580"
|
||||||
|
style="opacity:0.9;fill:#1a1a1a;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1" />
|
||||||
|
<text
|
||||||
|
id="text1584"
|
||||||
|
y="144.12724"
|
||||||
|
x="78.109634"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;line-height:1.25;font-family:'Roboto Condensed';-inkscape-font-specification:'Roboto Condensed, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
style="stroke-width:0.26458332"
|
||||||
|
y="144.12724"
|
||||||
|
x="78.109634"
|
||||||
|
id="tspan1582"
|
||||||
|
sodipodi:role="line">B</tspan></text>
|
||||||
|
<circle
|
||||||
|
style="opacity:0.9;fill:#1a1a1a;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
|
||||||
|
id="circle1586"
|
||||||
|
cx="92.041039"
|
||||||
|
cy="141.96429"
|
||||||
|
r="4.6772165" />
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;line-height:1.25;font-family:'Roboto Condensed';-inkscape-font-specification:'Roboto Condensed, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||||
|
x="90.280449"
|
||||||
|
y="144.12724"
|
||||||
|
id="text1590"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan1588"
|
||||||
|
x="90.280449"
|
||||||
|
y="144.12724"
|
||||||
|
style="stroke-width:0.26458332">X</tspan></text>
|
||||||
|
<circle
|
||||||
|
r="4.6772165"
|
||||||
|
cy="141.96429"
|
||||||
|
cx="104.74105"
|
||||||
|
id="circle1592"
|
||||||
|
style="opacity:0.9;fill:#1a1a1a;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1" />
|
||||||
|
<text
|
||||||
|
id="text1596"
|
||||||
|
y="144.12724"
|
||||||
|
x="102.98046"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;line-height:1.25;font-family:'Roboto Condensed';-inkscape-font-specification:'Roboto Condensed, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
style="stroke-width:0.26458332"
|
||||||
|
y="144.12724"
|
||||||
|
x="102.98046"
|
||||||
|
id="tspan1594"
|
||||||
|
sodipodi:role="line">Y</tspan></text>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(-48.12296,-137.22026)"
|
||||||
|
id="g1622"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
inkscape:label="Layer 1 copy">
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
|
||||||
|
d="m 52.800194,137.28692 a 4.6772165,4.6772165 0 0 0 -4.677234,4.67724 4.6772165,4.6772165 0 0 0 4.677234,4.67723 4.6772165,4.6772165 0 0 0 4.677234,-4.67723 4.6772165,4.6772165 0 0 0 -4.677234,-4.67724 z m -0.173116,2.32596 h 0.486792 l 1.457275,4.51445 h -0.58291 l -0.356567,-1.17822 H 52.10618 l -0.350367,1.17822 h -0.58291 z m 0.241846,0.79375 -0.613916,2.05259 h 1.230932 z"
|
||||||
|
id="circle1598"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
|
||||||
|
d="m 66.382135,137.28692 a 4.6772165,4.6772165 0 0 0 -4.677233,4.67724 4.6772165,4.6772165 0 0 0 4.677233,4.67723 4.6772165,4.6772165 0 0 0 4.677235,-4.67723 4.6772165,4.6772165 0 0 0 -4.677235,-4.67724 z m -1.273823,2.32596 h 1.286742 c 0.411345,0 0.720371,0.10025 0.927076,0.30076 0.208773,0.2005 0.313159,0.50126 0.313159,0.90227 0,0.21084 -0.05271,0.39687 -0.158129,0.5581 -0.10542,0.16123 -0.248046,0.28629 -0.427881,0.37517 0.206706,0.062 0.370004,0.18914 0.489892,0.38137 0.121957,0.19224 0.182935,0.42375 0.182935,0.69454 0,0.40514 -0.111622,0.72347 -0.334864,0.95498 -0.221173,0.23151 -0.537435,0.34726 -0.94878,0.34726 h -1.33015 z m 0.567406,0.48989 v 1.43557 h 0.728639 c 0.196371,0 0.354499,-0.0661 0.47439,-0.19844 0.121954,-0.13229 0.182933,-0.30799 0.182933,-0.52709 0,-0.24805 -0.05478,-0.42789 -0.16433,-0.53951 -0.109553,-0.11368 -0.276987,-0.17053 -0.502296,-0.17053 z m 0,1.91306 v 1.62471 h 0.775148 c 0.214973,0 0.385505,-0.0703 0.511595,-0.21084 0.126093,-0.14263 0.189138,-0.3421 0.189138,-0.59841 0,-0.54364 -0.229444,-0.81546 -0.68833,-0.81546 z"
|
||||||
|
id="circle1604"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
|
||||||
|
d="m 79.964081,137.28692 a 4.6772165,4.6772165 0 0 0 -4.677233,4.67724 4.6772165,4.6772165 0 0 0 4.677233,4.67723 4.6772165,4.6772165 0 0 0 4.677235,-4.67723 4.6772165,4.6772165 0 0 0 -4.677235,-4.67724 z m -1.524971,2.32596 h 0.663527 l 0.85576,1.73013 0.849562,-1.73013 h 0.666625 l -1.175123,2.23862 1.199928,2.27583 h -0.672828 l -0.868164,-1.76113 -0.871265,1.76113 h -0.672827 l 1.20613,-2.27583 z"
|
||||||
|
id="circle1610"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
|
||||||
|
d="m 93.546026,137.28692 a 4.6772165,4.6772165 0 0 0 -4.677233,4.67724 4.6772165,4.6772165 0 0 0 4.677233,4.67723 4.6772165,4.6772165 0 0 0 4.677235,-4.67723 4.6772165,4.6772165 0 0 0 -4.677235,-4.67724 z m -1.667598,2.32596 h 0.644922 l 0.923976,2.26653 0.923975,-2.26653 h 0.641821 l -1.283645,2.83083 v 1.68362 H 93.16207 v -1.68362 z"
|
||||||
|
id="circle1616"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 8.5 KiB |
Binary file not shown.
|
@ -0,0 +1,161 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
height="1000"
|
||||||
|
width="1000"
|
||||||
|
sodipodi:docname="abxy_font.svg"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1688"
|
||||||
|
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="1"
|
||||||
|
inkscape:cx="580.56811"
|
||||||
|
inkscape:cy="584.04581"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer2"
|
||||||
|
showgrid="false"
|
||||||
|
showguides="true"
|
||||||
|
inkscape:window-width="1727"
|
||||||
|
inkscape:window-height="1380"
|
||||||
|
inkscape:window-x="3633"
|
||||||
|
inkscape:window-y="576"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
showborder="true"
|
||||||
|
inkscape:showpageshadow="false">
|
||||||
|
<sodipodi:guide
|
||||||
|
id="guide_baseline"
|
||||||
|
inkscape:label="baseline"
|
||||||
|
position="0,253"
|
||||||
|
orientation="0,1"
|
||||||
|
inkscape:locked="false" />
|
||||||
|
<sodipodi:guide
|
||||||
|
id="guide_ascender"
|
||||||
|
inkscape:label="ascender"
|
||||||
|
position="0,945"
|
||||||
|
orientation="0,1"
|
||||||
|
inkscape:locked="false" />
|
||||||
|
<sodipodi:guide
|
||||||
|
id="guide_caps"
|
||||||
|
inkscape:label="caps"
|
||||||
|
position="0,896"
|
||||||
|
orientation="0,1"
|
||||||
|
inkscape:locked="false" />
|
||||||
|
<sodipodi:guide
|
||||||
|
id="guide_xheight"
|
||||||
|
inkscape:label="xheight"
|
||||||
|
position="0,729"
|
||||||
|
orientation="0,1"
|
||||||
|
inkscape:locked="false" />
|
||||||
|
<sodipodi:guide
|
||||||
|
id="guide_descender"
|
||||||
|
inkscape:label="descender"
|
||||||
|
position="0,28"
|
||||||
|
orientation="0,1"
|
||||||
|
inkscape:locked="false" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs4">
|
||||||
|
<font
|
||||||
|
horiz-adv-x="1024"
|
||||||
|
id="font2233"
|
||||||
|
inkscape:label="font 1">
|
||||||
|
<font-face
|
||||||
|
units-per-em="1024"
|
||||||
|
id="font-face2235"
|
||||||
|
font-family="ABXY" />
|
||||||
|
<missing-glyph
|
||||||
|
d="M0,0h1000v1024h-1000z"
|
||||||
|
id="missing-glyph2237" />
|
||||||
|
<glyph
|
||||||
|
glyph-name="Button_A"
|
||||||
|
id="glyph2313"
|
||||||
|
unicode="A"
|
||||||
|
d="M 500.00007,752.85352 A 237.17679,237.17679 0 0 1 262.82246,515.67551 237.17679,237.17679 0 0 1 500.00007,278.49804 237.17679,237.17679 0 0 1 737.17754,515.67551 237.17679,237.17679 0 0 1 500.00007,752.85352 Z M 491.2215,634.90651 h 24.68493 L 589.80332,405.98342 H 560.24448 L 542.1634,465.7298 h -77.35585 l -17.76686,-59.74638 h -29.5587 z m 12.26384,-40.25024 -31.13114,-104.0847 h 62.41953 z" />
|
||||||
|
<glyph
|
||||||
|
glyph-name="Button_B"
|
||||||
|
id="glyph2315"
|
||||||
|
d="M 500,752.85352 A 237.17679,237.17679 0 0 1 262.82239,515.67551 237.17679,237.17679 0 0 1 500,278.49804 237.17679,237.17679 0 0 1 737.17761,515.67551 237.17679,237.17679 0 0 1 500,752.85352 Z M 435.40562,634.90651 h 65.24952 c 20.85888,0 36.5291,-5.08361 47.01093,-15.25121 10.58662,-10.16722 15.88007,-25.41843 15.88007,-45.75326 0,-10.6914 -2.67302,-20.12485 -8.01866,-28.30062 -5.34563,-8.17576 -12.57819,-14.51744 -21.69729,-19.02454 10.48183,-3.14394 18.76237,-9.59108 24.84178,-19.33875 6.18444,-9.74834 9.2766,-21.48799 9.2766,-35.2195 0,-20.54426 -5.66026,-36.68635 -16.98063,-48.426 -11.21546,-11.73965 -27.25277,-17.60921 -48.11165,-17.60921 h -67.45054 z m 28.77261,-24.84178 v -72.79631 h 36.94851 c 9.95777,0 17.97629,3.3519 24.05582,10.0627 6.18432,6.70824 9.27633,15.6179 9.27633,26.72816 0,12.57833 -2.7778,21.69783 -8.33287,27.35795 -5.55546,5.76464 -14.04572,8.6475 -25.47088,8.6475 z m 0,-97.00937 v -82.38727 h 39.30691 c 10.9011,0 19.5486,3.56483 25.94248,10.6914 6.39402,7.23269 9.59109,17.34758 9.59109,30.34479 0,27.56738 -11.635,41.35108 -34.90459,41.35108 z"
|
||||||
|
unicode="B" />
|
||||||
|
<glyph
|
||||||
|
glyph-name="Button_X"
|
||||||
|
id="glyph2317"
|
||||||
|
d="M 500,752.85352 A 237.17679,237.17679 0 0 1 262.82226,515.67551 237.17679,237.17679 0 0 1 500,278.49804 237.17679,237.17679 0 0 1 737.17774,515.67551 237.17679,237.17679 0 0 1 500,752.85352 Z M 422.67044,634.90651 h 33.64651 l 43.39486,-87.73318 43.0805,87.73318 h 33.80376 L 517.0068,521.38836 577.85388,405.98342 h -34.11837 l -44.0237,89.30508 -44.18095,-89.30508 h -34.11837 l 61.16157,115.40494 z"
|
||||||
|
unicode="X" />
|
||||||
|
<glyph
|
||||||
|
glyph-name="Button_Y"
|
||||||
|
id="glyph2319"
|
||||||
|
d="M 500,752.85352 A 237.17679,237.17679 0 0 1 262.82239,515.67551 237.17679,237.17679 0 0 1 500,278.49804 237.17679,237.17679 0 0 1 737.17761,515.67551 237.17679,237.17679 0 0 1 500,752.85352 Z M 415.43788,634.90651 h 32.70331 l 46.85369,-114.93335 46.85382,114.93335 h 32.54622 L 509.3025,491.35806 v -85.37464 h -28.77261 v 85.37464 z"
|
||||||
|
unicode="Y" />
|
||||||
|
</font>
|
||||||
|
</defs>
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
id="layer1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
inkscape:label="A"
|
||||||
|
style="display:none">
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:13.41674423;stroke-opacity:1"
|
||||||
|
d="M 500.00007,271.14648 A 237.17679,237.17679 0 0 0 262.82246,508.32449 237.17679,237.17679 0 0 0 500.00007,745.50196 237.17679,237.17679 0 0 0 737.17754,508.32449 237.17679,237.17679 0 0 0 500.00007,271.14648 Z m -8.77857,117.94701 h 24.68493 l 73.89689,228.92309 H 560.24448 L 542.1634,558.2702 h -77.35585 l -17.76686,59.74638 h -29.5587 z m 12.26384,40.25024 -31.13114,104.0847 h 62.41953 z"
|
||||||
|
id="circle1598"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer4"
|
||||||
|
inkscape:label="B"
|
||||||
|
style="display:none">
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:13.41674423;stroke-opacity:1"
|
||||||
|
d="M 500,271.14648 A 237.17679,237.17679 0 0 0 262.82239,508.32449 237.17679,237.17679 0 0 0 500,745.50196 237.17679,237.17679 0 0 0 737.17761,508.32449 237.17679,237.17679 0 0 0 500,271.14648 Z m -64.59438,117.94701 h 65.24952 c 20.85888,0 36.5291,5.08361 47.01093,15.25121 10.58662,10.16722 15.88007,25.41843 15.88007,45.75326 0,10.6914 -2.67302,20.12485 -8.01866,28.30062 -5.34563,8.17576 -12.57819,14.51744 -21.69729,19.02454 10.48183,3.14394 18.76237,9.59108 24.84178,19.33875 6.18444,9.74834 9.2766,21.48799 9.2766,35.2195 0,20.54426 -5.66026,36.68635 -16.98063,48.426 -11.21546,11.73965 -27.25277,17.60921 -48.11165,17.60921 h -67.45054 z m 28.77261,24.84178 v 72.79631 h 36.94851 c 9.95777,0 17.97629,-3.3519 24.05582,-10.0627 6.18432,-6.70824 9.27633,-15.6179 9.27633,-26.72816 0,-12.57833 -2.7778,-21.69783 -8.33287,-27.35795 -5.55546,-5.76464 -14.04572,-8.6475 -25.47088,-8.6475 z m 0,97.00937 v 82.38727 h 39.30691 c 10.9011,0 19.5486,-3.56483 25.94248,-10.6914 6.39402,-7.23269 9.59109,-17.34758 9.59109,-30.34479 0,-27.56738 -11.635,-41.35108 -34.90459,-41.35108 z"
|
||||||
|
id="circle1604"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer3"
|
||||||
|
inkscape:label="X"
|
||||||
|
style="display:none">
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:13.41674423;stroke-opacity:1"
|
||||||
|
d="M 500,271.14648 A 237.17679,237.17679 0 0 0 262.82226,508.32449 237.17679,237.17679 0 0 0 500,745.50196 237.17679,237.17679 0 0 0 737.17774,508.32449 237.17679,237.17679 0 0 0 500,271.14648 Z m -77.32956,117.94701 h 33.64651 l 43.39486,87.73318 43.0805,-87.73318 h 33.80376 l -59.58927,113.51815 60.84708,115.40494 h -34.11837 l -44.0237,-89.30508 -44.18095,89.30508 h -34.11837 l 61.16157,-115.40494 z"
|
||||||
|
id="circle1610"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer2"
|
||||||
|
inkscape:label="Y"
|
||||||
|
style="display:inline">
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:13.41674423;stroke-opacity:1"
|
||||||
|
d="M 500,271.14648 A 237.17679,237.17679 0 0 0 262.82239,508.32449 237.17679,237.17679 0 0 0 500,745.50196 237.17679,237.17679 0 0 0 737.17761,508.32449 237.17679,237.17679 0 0 0 500,271.14648 Z m -84.56212,117.94701 h 32.70331 l 46.85369,114.93335 46.85382,-114.93335 h 32.54622 L 509.3025,532.64194 v 85.37464 h -28.77261 v -85.37464 z"
|
||||||
|
id="circle1616"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 8.6 KiB |
Binary file not shown.
|
@ -2,7 +2,10 @@ pfiles = [
|
||||||
'controller_mask.png',
|
'controller_mask.png',
|
||||||
'logo_sdf.png',
|
'logo_sdf.png',
|
||||||
'xemu_64x64.png',
|
'xemu_64x64.png',
|
||||||
'roboto_medium.ttf',
|
'abxy.ttf',
|
||||||
|
'Roboto-Medium.ttf',
|
||||||
|
'RobotoCondensed-Regular.ttf',
|
||||||
|
'font_awesome_6_1_1_solid.otf',
|
||||||
]
|
]
|
||||||
|
|
||||||
libpfile_targets = []
|
libpfile_targets = []
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 8220e8748922ddd9bba4cbacd608601586c9a4bb
|
Subproject commit 5da3fd2463288d9e048dbf3ea41f2bad0a4287a8
|
|
@ -297,6 +297,27 @@ void mcpx_apu_debug_toggle_mute(uint16_t v)
|
||||||
g_dbg_muted_voices[v / 64] ^= (1LL << (v % 64));
|
g_dbg_muted_voices[v / 64] ^= (1LL << (v % 64));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void mcpx_apu_update_dsp_preference(MCPXAPUState *d)
|
||||||
|
{
|
||||||
|
static int last_known_preference = -1;
|
||||||
|
|
||||||
|
if (last_known_preference == (int)g_config.audio.use_dsp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_config.audio.use_dsp) {
|
||||||
|
d->mon = MCPX_APU_DEBUG_MON_GP_OR_EP;
|
||||||
|
d->gp.realtime = true;
|
||||||
|
d->ep.realtime = true;
|
||||||
|
} else {
|
||||||
|
d->mon = MCPX_APU_DEBUG_MON_VP;
|
||||||
|
d->gp.realtime = false;
|
||||||
|
d->ep.realtime = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
last_known_preference = g_config.audio.use_dsp;
|
||||||
|
}
|
||||||
|
|
||||||
static float clampf(float v, float min, float max)
|
static float clampf(float v, float min, float max)
|
||||||
{
|
{
|
||||||
if (v < min) {
|
if (v < min) {
|
||||||
|
@ -2050,6 +2071,7 @@ static int voice_get_samples(MCPXAPUState *d, uint32_t v, float samples[][2],
|
||||||
|
|
||||||
static void se_frame(MCPXAPUState *d)
|
static void se_frame(MCPXAPUState *d)
|
||||||
{
|
{
|
||||||
|
mcpx_apu_update_dsp_preference(d);
|
||||||
mcpx_debug_begin_frame();
|
mcpx_debug_begin_frame();
|
||||||
g_dbg.gp_realtime = d->gp.realtime;
|
g_dbg.gp_realtime = d->gp.realtime;
|
||||||
g_dbg.ep_realtime = d->ep.realtime;
|
g_dbg.ep_realtime = d->ep.realtime;
|
||||||
|
@ -2137,6 +2159,7 @@ static void se_frame(MCPXAPUState *d)
|
||||||
d->apu_fifo_output[off + i][0] += isamp[2*i];
|
d->apu_fifo_output[off + i][0] += isamp[2*i];
|
||||||
d->apu_fifo_output[off + i][1] += isamp[2*i+1];
|
d->apu_fifo_output[off + i][1] += isamp[2*i+1];
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(d->vp.sample_buf, 0, sizeof(d->vp.sample_buf));
|
memset(d->vp.sample_buf, 0, sizeof(d->vp.sample_buf));
|
||||||
memset(mixbins, 0, sizeof(mixbins));
|
memset(mixbins, 0, sizeof(mixbins));
|
||||||
}
|
}
|
||||||
|
@ -2198,6 +2221,15 @@ static void se_frame(MCPXAPUState *d)
|
||||||
fwrite(d->apu_fifo_output, sizeof(d->apu_fifo_output), 1, fd);
|
fwrite(d->apu_fifo_output, sizeof(d->apu_fifo_output), 1, fd);
|
||||||
fclose(fd);
|
fclose(fd);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (0 <= g_config.audio.volume_limit && g_config.audio.volume_limit < 1) {
|
||||||
|
float f = pow(g_config.audio.volume_limit, M_E);
|
||||||
|
for (int i = 0; i < 256; i++) {
|
||||||
|
d->apu_fifo_output[i][0] *= f;
|
||||||
|
d->apu_fifo_output[i][1] *= f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
qemu_spin_lock(&d->vp.out_buf_lock);
|
qemu_spin_lock(&d->vp.out_buf_lock);
|
||||||
int num_bytes_free = fifo8_num_free(&d->vp.out_buf);
|
int num_bytes_free = fifo8_num_free(&d->vp.out_buf);
|
||||||
assert(num_bytes_free >= sizeof(d->apu_fifo_output));
|
assert(num_bytes_free >= sizeof(d->apu_fifo_output));
|
||||||
|
@ -2624,15 +2656,7 @@ void mcpx_apu_init(PCIBus *bus, int devfn, MemoryRegion *ram)
|
||||||
/* Until DSP is more performant, a switch to decide whether or not we should
|
/* Until DSP is more performant, a switch to decide whether or not we should
|
||||||
* use the full audio pipeline or not.
|
* use the full audio pipeline or not.
|
||||||
*/
|
*/
|
||||||
if (g_config.audio.use_dsp) {
|
mcpx_apu_update_dsp_preference(d);
|
||||||
d->mon = MCPX_APU_DEBUG_MON_GP_OR_EP;
|
|
||||||
d->gp.realtime = true;
|
|
||||||
d->ep.realtime = true;
|
|
||||||
} else {
|
|
||||||
d->mon = MCPX_APU_DEBUG_MON_VP;
|
|
||||||
d->gp.realtime = false;
|
|
||||||
d->ep.realtime = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
qemu_thread_create(&d->apu_thread, "mcpx.apu_thread", mcpx_apu_frame_thread,
|
qemu_thread_create(&d->apu_thread, "mcpx.apu_thread", mcpx_apu_frame_thread,
|
||||||
d, QEMU_THREAD_JOINABLE);
|
d, QEMU_THREAD_JOINABLE);
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
Fonticons, Inc. (https://fontawesome.com)
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Font Awesome Free License
|
||||||
|
|
||||||
|
Font Awesome Free is free, open source, and GPL friendly. You can use it for
|
||||||
|
commercial projects, open source projects, or really almost whatever you want.
|
||||||
|
Full Font Awesome Free license: https://fontawesome.com/license/free.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/)
|
||||||
|
|
||||||
|
The Font Awesome Free download is licensed under a Creative Commons
|
||||||
|
Attribution 4.0 International License and applies to all icons packaged
|
||||||
|
as SVG and JS file types.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Fonts: SIL OFL 1.1 License
|
||||||
|
|
||||||
|
In the Font Awesome Free download, the SIL OFL license applies to all icons
|
||||||
|
packaged as web and desktop font files.
|
||||||
|
|
||||||
|
Copyright (c) 2022 Fonticons, Inc. (https://fontawesome.com)
|
||||||
|
with Reserved Font Name: "Font Awesome".
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
SIL OPEN FONT LICENSE
|
||||||
|
Version 1.1 - 26 February 2007
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting — in part or in whole — any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Code: MIT License (https://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
In the Font Awesome Free download, the MIT license applies to all non-font and
|
||||||
|
non-icon files.
|
||||||
|
|
||||||
|
Copyright 2022 Fonticons, Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without limitation the rights to use, copy,
|
||||||
|
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
and to permit persons to whom the Software is furnished to do so, subject to the
|
||||||
|
following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Attribution
|
||||||
|
|
||||||
|
Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font
|
||||||
|
Awesome Free files already contain embedded comments with sufficient
|
||||||
|
attribution, so you shouldn't need to do anything additional when using these
|
||||||
|
files normally.
|
||||||
|
|
||||||
|
We've kept attribution comments terse, so we ask that you do not actively work
|
||||||
|
to remove them from files, especially code. They're a great way for folks to
|
||||||
|
learn about Font Awesome.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Brand Icons
|
||||||
|
|
||||||
|
All brand icons are trademarks of their respective owners. The use of these
|
||||||
|
trademarks does not indicate endorsement of the trademark holder by Font
|
||||||
|
Awesome, nor vice versa. **Please do not use brand logos for any purpose except
|
||||||
|
to represent the company, product, or service to which they refer.**
|
|
@ -0,0 +1,24 @@
|
||||||
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
distribute this software, either in source code form or as a compiled
|
||||||
|
binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
means.
|
||||||
|
|
||||||
|
In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
of this software dedicate any and all copyright interest in the
|
||||||
|
software to the public domain. We make this dedication for the benefit
|
||||||
|
of the public at large and to the detriment of our heirs and
|
||||||
|
successors. We intend this dedication to be an overt act of
|
||||||
|
relinquishment in perpetuity of all present and future rights to this
|
||||||
|
software under copyright law.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
For more information, please refer to <http://unlicense.org/>
|
|
@ -0,0 +1,16 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
||||||
|
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
||||||
|
Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||||
|
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -641,6 +641,15 @@ static SlirpState *slirp_lookup(Monitor *mon, const char *id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef XBOX
|
||||||
|
void *slirp_get_state_from_netdev(const char *id)
|
||||||
|
{
|
||||||
|
SlirpState *s = slirp_lookup(NULL, id);
|
||||||
|
if (!s) return NULL;
|
||||||
|
return s->slirp;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void hmp_hostfwd_remove(Monitor *mon, const QDict *qdict)
|
void hmp_hostfwd_remove(Monitor *mon, const QDict *qdict)
|
||||||
{
|
{
|
||||||
struct in_addr host_addr = { .s_addr = INADDR_ANY };
|
struct in_addr host_addr = { .s_addr = INADDR_ANY };
|
||||||
|
|
|
@ -28,7 +28,7 @@ sub_file="${sub_tdir}/submodule.tar"
|
||||||
# different to the host OS.
|
# different to the host OS.
|
||||||
submodules="dtc slirp meson ui/keycodemapdb"
|
submodules="dtc slirp meson ui/keycodemapdb"
|
||||||
submodules="$submodules tests/fp/berkeley-softfloat-3 tests/fp/berkeley-testfloat-3"
|
submodules="$submodules tests/fp/berkeley-softfloat-3 tests/fp/berkeley-testfloat-3"
|
||||||
submodules="$submodules ui/imgui ui/implot util/xxHash tomlplusplus genconfig" # xemu extras
|
submodules="$submodules ui/thirdparty/imgui ui/thirdparty/implot util/xxHash tomlplusplus genconfig" # xemu extras
|
||||||
sub_deinit=""
|
sub_deinit=""
|
||||||
|
|
||||||
function cleanup() {
|
function cleanup() {
|
||||||
|
|
|
@ -20,6 +20,7 @@ bsd_3clause = 'bsd-3clause'
|
||||||
zlib = 'zlib'
|
zlib = 'zlib'
|
||||||
lgplv2_1 = 'lgplv2_1'
|
lgplv2_1 = 'lgplv2_1'
|
||||||
apache2 = 'apache2'
|
apache2 = 'apache2'
|
||||||
|
unlicense = 'unlicense'
|
||||||
multi = 'multi'
|
multi = 'multi'
|
||||||
|
|
||||||
|
|
||||||
|
@ -179,13 +180,13 @@ Lib('slirp', 'https://gitlab.freedesktop.org/slirp',
|
||||||
Lib('imgui', 'https://github.com/ocornut/imgui',
|
Lib('imgui', 'https://github.com/ocornut/imgui',
|
||||||
mit, 'https://raw.githubusercontent.com/ocornut/imgui/master/LICENSE.txt',
|
mit, 'https://raw.githubusercontent.com/ocornut/imgui/master/LICENSE.txt',
|
||||||
ships_static=all_platforms,
|
ships_static=all_platforms,
|
||||||
submodule=Submodule('ui/imgui')
|
submodule=Submodule('ui/thirdparty/imgui')
|
||||||
),
|
),
|
||||||
|
|
||||||
Lib('implot', 'https://github.com/epezent/implot',
|
Lib('implot', 'https://github.com/epezent/implot',
|
||||||
mit, 'https://raw.githubusercontent.com/epezent/implot/master/LICENSE',
|
mit, 'https://raw.githubusercontent.com/epezent/implot/master/LICENSE',
|
||||||
ships_static=all_platforms,
|
ships_static=all_platforms,
|
||||||
submodule=Submodule('ui/implot')
|
submodule=Submodule('ui/thirdparty/implot')
|
||||||
),
|
),
|
||||||
|
|
||||||
Lib('httplib', 'https://github.com/yhirose/cpp-httplib',
|
Lib('httplib', 'https://github.com/yhirose/cpp-httplib',
|
||||||
|
@ -206,10 +207,10 @@ Lib('stb_image', 'https://github.com/nothings/stb',
|
||||||
version='2.25'
|
version='2.25'
|
||||||
),
|
),
|
||||||
|
|
||||||
Lib('inih', 'https://github.com/benhoyt/inih',
|
Lib('tomlplusplus', 'https://github.com/marzer/tomlplusplus',
|
||||||
bsd, 'https://raw.githubusercontent.com/mborgerson/xemu/master/ui/inih/LICENSE.txt',
|
mit, 'https://raw.githubusercontent.com/marzer/tomlplusplus/master/LICENSE',
|
||||||
ships_static=all_platforms,
|
ships_static=all_platforms,
|
||||||
version='351217124ddb3e3fe2b982248a04c672350bb0af'
|
submodule=Submodule('tomlplusplus')
|
||||||
),
|
),
|
||||||
|
|
||||||
Lib('xxHash', 'https://github.com/Cyan4973/xxHash.git',
|
Lib('xxHash', 'https://github.com/Cyan4973/xxHash.git',
|
||||||
|
@ -218,6 +219,12 @@ Lib('xxHash', 'https://github.com/Cyan4973/xxHash.git',
|
||||||
submodule=Submodule('util/xxHash')
|
submodule=Submodule('util/xxHash')
|
||||||
),
|
),
|
||||||
|
|
||||||
|
Lib('fpng', 'https://github.com/richgel999/fpng',
|
||||||
|
unlicense, 'https://github.com/richgel999/fpng/blob/main/README.md',
|
||||||
|
ships_static=all_platforms,
|
||||||
|
version='6926f5a0a78f22d42b074a0ab8032e07736babd4'
|
||||||
|
),
|
||||||
|
|
||||||
#
|
#
|
||||||
# Data files included with xemu
|
# Data files included with xemu
|
||||||
#
|
#
|
||||||
|
@ -228,6 +235,12 @@ Lib('roboto', 'https://github.com/googlefonts/roboto',
|
||||||
version='2.138'
|
version='2.138'
|
||||||
),
|
),
|
||||||
|
|
||||||
|
Lib('fontawesome', 'https://fontawesome.com',
|
||||||
|
multi, '',
|
||||||
|
ships_static=all_platforms,
|
||||||
|
version='6.1.1'
|
||||||
|
),
|
||||||
|
|
||||||
#
|
#
|
||||||
# Libraries either linked statically, dynamically linked & shipped, or dynamically linked with system-installed libraries only
|
# Libraries either linked statically, dynamically linked & shipped, or dynamically linked with system-installed libraries only
|
||||||
#
|
#
|
||||||
|
|
17
softmmu/vl.c
17
softmmu/vl.c
|
@ -2866,10 +2866,21 @@ void qemu_init(int argc, char **argv, char **envp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fake_argv[fake_argc++] = g_strdup_printf("xbox%s%s%s",
|
const char *avpack_str = (const char *[]){
|
||||||
|
"scart",
|
||||||
|
"hdtv",
|
||||||
|
"vga",
|
||||||
|
"rfu",
|
||||||
|
"svideo",
|
||||||
|
"composite",
|
||||||
|
"none",
|
||||||
|
}[g_config.sys.avpack];
|
||||||
|
|
||||||
|
fake_argv[fake_argc++] = g_strdup_printf("xbox%s%s%s,avpack=%s",
|
||||||
(bootrom_arg != NULL) ? bootrom_arg : "",
|
(bootrom_arg != NULL) ? bootrom_arg : "",
|
||||||
g_config.general.misc.skip_boot_anim ? ",short-animation=on" : "",
|
g_config.general.skip_boot_anim ? ",short-animation=on" : "",
|
||||||
",kernel-irqchip=off"
|
",kernel-irqchip=off",
|
||||||
|
avpack_str
|
||||||
);
|
);
|
||||||
|
|
||||||
if (bootrom_arg != NULL) {
|
if (bootrom_arg != NULL) {
|
||||||
|
|
1
ui/imgui
1
ui/imgui
|
@ -1 +0,0 @@
|
||||||
Subproject commit e18abe3619cfa0eced163c027d0349506814816c
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit a6bab98517b1baa3116db52518dda1eb2d7eaab7
|
|
|
@ -15,57 +15,35 @@ softmmu_ss.add(files(
|
||||||
'udmabuf.c',
|
'udmabuf.c',
|
||||||
))
|
))
|
||||||
|
|
||||||
imgui_files = files(
|
subdir('thirdparty')
|
||||||
'imgui/imgui.cpp',
|
|
||||||
'imgui/imgui_draw.cpp',
|
|
||||||
'imgui/imgui_tables.cpp',
|
|
||||||
#'imgui/imgui_demo.cpp',
|
|
||||||
'imgui/imgui_widgets.cpp',
|
|
||||||
'imgui/backends/imgui_impl_opengl3.cpp',
|
|
||||||
'imgui/backends/imgui_impl_sdl.cpp',
|
|
||||||
'implot/implot.cpp',
|
|
||||||
#'implot/implot_demo.cpp',
|
|
||||||
'implot/implot_items.cpp'
|
|
||||||
)
|
|
||||||
|
|
||||||
imgui_cppargs = ['-DIMGUI_IMPL_OPENGL_LOADER_CUSTOM="epoxy/gl.h"']
|
|
||||||
|
|
||||||
libimgui = static_library('imgui',
|
|
||||||
sources: imgui_files,
|
|
||||||
cpp_args: imgui_cppargs,
|
|
||||||
include_directories: 'imgui',
|
|
||||||
dependencies: [sdl, opengl])
|
|
||||||
imgui = declare_dependency(link_with: libimgui,
|
|
||||||
compile_args: imgui_cppargs,
|
|
||||||
include_directories: 'imgui')
|
|
||||||
|
|
||||||
xemu_ss = ss.source_set()
|
xemu_ss = ss.source_set()
|
||||||
xemu_ss.add(files(
|
xemu_ss.add(files(
|
||||||
'xemu.c',
|
|
||||||
'xemu-custom-widgets.c',
|
|
||||||
'xemu-data.c',
|
|
||||||
'xemu-input.c',
|
'xemu-input.c',
|
||||||
'xemu-monitor.c',
|
'xemu-monitor.c',
|
||||||
'xemu-net.c',
|
'xemu-net.c',
|
||||||
'xemu-settings.cc',
|
'xemu-settings.cc',
|
||||||
'xemu-shaders.c',
|
|
||||||
'xemu-hud.cc',
|
'xemu.c',
|
||||||
'xemu-reporting.cc',
|
'xemu-data.c',
|
||||||
))
|
))
|
||||||
|
|
||||||
xemu_ss.add(when: 'CONFIG_WIN32', if_true: files('xemu-update.cc'))
|
subdir('xui')
|
||||||
|
|
||||||
if 'CONFIG_DARWIN' in config_host
|
if 'CONFIG_DARWIN' in config_host
|
||||||
xemu_cocoa = dependency('appleframeworks', modules: 'Cocoa')
|
xemu_cocoa = dependency('appleframeworks', modules: 'Cocoa')
|
||||||
xemu_ss.add(xemu_cocoa) # FIXME: Use existing cocoa name
|
xemu_ss.add(xemu_cocoa)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
xemu_ss.add(imgui, sdl, opengl, openssl)
|
if 'CONFIG_LINUX' in config_host
|
||||||
xemu_ss.add(when: 'CONFIG_LINUX', if_true: [xemu_gtk, files('xemu-os-utils-linux.c', 'noc_file_dialog_gtk.c')])
|
xemu_ss.add(xemu_gtk)
|
||||||
xemu_ss.add(when: 'CONFIG_WIN32', if_true: files('xemu-os-utils-windows.c', 'noc_file_dialog_win32.c'))
|
endif
|
||||||
xemu_ss.add(when: 'CONFIG_DARWIN', if_true: files('xemu-os-utils-macos.m', 'noc_file_dialog_macos.m'))
|
|
||||||
|
|
||||||
|
xemu_ss.add(when: 'CONFIG_LINUX', if_true: [xemu_gtk, files('xemu-os-utils-linux.c')])
|
||||||
|
xemu_ss.add(when: 'CONFIG_WIN32', if_true: files('xemu-os-utils-windows.c'))
|
||||||
|
xemu_ss.add(when: 'CONFIG_DARWIN', if_true: files('xemu-os-utils-macos.m'))
|
||||||
xemu_ss.add(when: 'CONFIG_RENDERDOC', if_true: [libdl])
|
xemu_ss.add(when: 'CONFIG_RENDERDOC', if_true: [libdl])
|
||||||
|
xemu_ss.add(imgui, implot, stb_image, noc, sdl, opengl, openssl, fa, fpng, json, httplib)
|
||||||
|
|
||||||
softmmu_ss.add_all(xemu_ss)
|
softmmu_ss.add_all(xemu_ss)
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1 @@
|
||||||
|
645d49cf6b2e82ce25b5b59f6a2e2df30e6f5fa6
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,122 @@
|
||||||
|
// fpng.h - unlicense (see end of fpng.cpp)
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#ifndef FPNG_TRAIN_HUFFMAN_TABLES
|
||||||
|
// Set to 1 when using the -t (training) option in fpng_test to generate new opaque/alpha Huffman tables for the single pass encoder.
|
||||||
|
#define FPNG_TRAIN_HUFFMAN_TABLES (0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace fpng
|
||||||
|
{
|
||||||
|
// ---- Library initialization - call once to identify if the processor supports SSE.
|
||||||
|
// Otherwise you'll only get scalar fallbacks.
|
||||||
|
void fpng_init();
|
||||||
|
|
||||||
|
// ---- Useful Utilities
|
||||||
|
|
||||||
|
// Returns true if the CPU supports SSE 4.1, and SSE support wasn't disabled by setting FPNG_NO_SSE=1.
|
||||||
|
// fpng_init() must have been called first, or it'll assert and return false.
|
||||||
|
bool fpng_cpu_supports_sse41();
|
||||||
|
|
||||||
|
// Fast CRC-32 SSE4.1+pclmul or a scalar fallback (slice by 4)
|
||||||
|
const uint32_t FPNG_CRC32_INIT = 0;
|
||||||
|
uint32_t fpng_crc32(const void* pData, size_t size, uint32_t prev_crc32 = FPNG_CRC32_INIT);
|
||||||
|
|
||||||
|
// Fast Adler32 SSE4.1 Adler-32 with a scalar fallback.
|
||||||
|
const uint32_t FPNG_ADLER32_INIT = 1;
|
||||||
|
uint32_t fpng_adler32(const void* pData, size_t size, uint32_t adler = FPNG_ADLER32_INIT);
|
||||||
|
|
||||||
|
// ---- Compression
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
// Enables computing custom Huffman tables for each file, instead of using the custom global tables.
|
||||||
|
// Results in roughly 6% smaller files on average, but compression is around 40% slower.
|
||||||
|
FPNG_ENCODE_SLOWER = 1,
|
||||||
|
|
||||||
|
// Only use raw Deflate blocks (no compression at all). Intended for testing.
|
||||||
|
FPNG_FORCE_UNCOMPRESSED = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fast PNG encoding. The resulting file can be decoded either using a standard PNG decoder or by the fpng_decode_memory() function below.
|
||||||
|
// pImage: pointer to RGB or RGBA image pixels, R first in memory, B/A last.
|
||||||
|
// w/h - image dimensions. Image's row pitch in bytes must is w*num_chans.
|
||||||
|
// num_chans must be 3 or 4.
|
||||||
|
bool fpng_encode_image_to_memory(const void* pImage, uint32_t w, uint32_t h, uint32_t num_chans, std::vector<uint8_t>& out_buf, uint32_t flags = 0);
|
||||||
|
|
||||||
|
#ifndef FPNG_NO_STDIO
|
||||||
|
// Fast PNG encoding to the specified file.
|
||||||
|
bool fpng_encode_image_to_file(const char* pFilename, const void* pImage, uint32_t w, uint32_t h, uint32_t num_chans, uint32_t flags = 0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ---- Decompression
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
FPNG_DECODE_SUCCESS = 0, // file is a valid PNG file and written by FPNG and the decode succeeded
|
||||||
|
|
||||||
|
FPNG_DECODE_NOT_FPNG, // file is a valid PNG file, but it wasn't written by FPNG so you should try decoding it with a general purpose PNG decoder
|
||||||
|
|
||||||
|
FPNG_DECODE_INVALID_ARG, // invalid function parameter
|
||||||
|
|
||||||
|
FPNG_DECODE_FAILED_NOT_PNG, // file cannot be a PNG file
|
||||||
|
FPNG_DECODE_FAILED_HEADER_CRC32, // a chunk CRC32 check failed, file is likely corrupted or not PNG
|
||||||
|
FPNG_DECODE_FAILED_INVALID_DIMENSIONS, // invalid image dimensions in IHDR chunk (0 or too large)
|
||||||
|
FPNG_DECODE_FAILED_DIMENSIONS_TOO_LARGE, // decoding the file fully into memory would likely require too much memory (only on 32bpp builds)
|
||||||
|
FPNG_DECODE_FAILED_CHUNK_PARSING, // failed while parsing the chunk headers, or file is corrupted
|
||||||
|
FPNG_DECODE_FAILED_INVALID_IDAT, // IDAT data length is too small and cannot be valid, file is either corrupted or it's a bug
|
||||||
|
|
||||||
|
// fpng_decode_file() specific errors
|
||||||
|
FPNG_DECODE_FILE_OPEN_FAILED,
|
||||||
|
FPNG_DECODE_FILE_TOO_LARGE,
|
||||||
|
FPNG_DECODE_FILE_READ_FAILED,
|
||||||
|
FPNG_DECODE_FILE_SEEK_FAILED
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fast PNG decoding of files ONLY created by fpng_encode_image_to_memory() or fpng_encode_image_to_file().
|
||||||
|
// If fpng_get_info() or fpng_decode_memory() returns FPNG_DECODE_NOT_FPNG, you should decode the PNG by falling back to a general purpose decoder.
|
||||||
|
//
|
||||||
|
// fpng_get_info() parses the PNG header and iterates through all chunks to determine if it's a file written by FPNG, but does not decompress the actual image data so it's relatively fast.
|
||||||
|
//
|
||||||
|
// pImage, image_size: Pointer to PNG image data and its size
|
||||||
|
// width, height: output image's dimensions
|
||||||
|
// channels_in_file: will be 3 or 4
|
||||||
|
//
|
||||||
|
// Returns FPNG_DECODE_SUCCESS on success, otherwise one of the failure codes above.
|
||||||
|
// If FPNG_DECODE_NOT_FPNG is returned, you must decompress the file with a general purpose PNG decoder.
|
||||||
|
// If another error occurs, the file is likely corrupted or invalid, but you can still try to decompress the file with another decoder (which will likely fail).
|
||||||
|
int fpng_get_info(const void* pImage, uint32_t image_size, uint32_t& width, uint32_t& height, uint32_t& channels_in_file);
|
||||||
|
|
||||||
|
// fpng_decode_memory() decompresses 24/32bpp PNG files ONLY encoded by this module.
|
||||||
|
// If the image was written by FPNG, it will decompress the image data, otherwise it will return FPNG_DECODE_NOT_FPNG in which case you should fall back to a general purpose PNG decoder (lodepng, stb_image, libpng, etc.)
|
||||||
|
//
|
||||||
|
// pImage, image_size: Pointer to PNG image data and its size
|
||||||
|
// out: Output 24/32bpp image buffer
|
||||||
|
// width, height: output image's dimensions
|
||||||
|
// channels_in_file: will be 3 or 4
|
||||||
|
// desired_channels: must be 3 or 4
|
||||||
|
//
|
||||||
|
// If the image is 24bpp and 32bpp is requested, the alpha values will be set to 0xFF.
|
||||||
|
// If the image is 32bpp and 24bpp is requested, the alpha values will be discarded.
|
||||||
|
//
|
||||||
|
// Returns FPNG_DECODE_SUCCESS on success, otherwise one of the failure codes above.
|
||||||
|
// If FPNG_DECODE_NOT_FPNG is returned, you must decompress the file with a general purpose PNG decoder.
|
||||||
|
// If another error occurs, the file is likely corrupted or invalid, but you can still try to decompress the file with another decoder (which will likely fail).
|
||||||
|
int fpng_decode_memory(const void* pImage, uint32_t image_size, std::vector<uint8_t>& out, uint32_t& width, uint32_t& height, uint32_t& channels_in_file, uint32_t desired_channels);
|
||||||
|
|
||||||
|
#ifndef FPNG_NO_STDIO
|
||||||
|
int fpng_decode_file(const char* pFilename, std::vector<uint8_t>& out, uint32_t& width, uint32_t& height, uint32_t& channels_in_file, uint32_t desired_channels);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ---- Internal API used for Huffman table training purposes
|
||||||
|
|
||||||
|
#if FPNG_TRAIN_HUFFMAN_TABLES
|
||||||
|
const uint32_t HUFF_COUNTS_SIZE = 288;
|
||||||
|
extern uint64_t g_huff_counts[HUFF_COUNTS_SIZE];
|
||||||
|
bool create_dynamic_block_prefix(uint64_t* pFreq, uint32_t num_chans, std::vector<uint8_t>& prefix, uint64_t& bit_buf, int& bit_buf_size, uint32_t *pCodes, uint8_t *pCodesizes);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace fpng
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit c71a50deb5ddf1ea386b91e60fa2e4a26d080074
|
|
@ -0,0 +1 @@
|
||||||
|
#include <epoxy/gl.h>
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit b47c8bacdbc78bc521691f70666f13924bb522ab
|
|
@ -0,0 +1,63 @@
|
||||||
|
imgui_files = files(
|
||||||
|
'imgui/imgui.cpp',
|
||||||
|
'imgui/imgui_draw.cpp',
|
||||||
|
'imgui/imgui_tables.cpp',
|
||||||
|
'imgui/imgui_widgets.cpp',
|
||||||
|
'imgui/backends/imgui_impl_sdl.cpp',
|
||||||
|
'imgui/backends/imgui_impl_opengl3.cpp',
|
||||||
|
#'imgui/imgui_demo.cpp',
|
||||||
|
)
|
||||||
|
|
||||||
|
imgui_cppargs = ['-DIMGUI_IMPL_OPENGL_LOADER_CUSTOM',
|
||||||
|
'-include', 'imgui_impl_opengl3_loader_override.h']
|
||||||
|
|
||||||
|
libimgui = static_library('imgui',
|
||||||
|
sources: imgui_files,
|
||||||
|
cpp_args: imgui_cppargs,
|
||||||
|
include_directories: ['.', 'imgui'],
|
||||||
|
dependencies: [sdl, opengl])
|
||||||
|
imgui = declare_dependency(link_with: libimgui,
|
||||||
|
include_directories: ['imgui', 'imgui/backends'])
|
||||||
|
|
||||||
|
implot_files = files(
|
||||||
|
'implot/implot.cpp',
|
||||||
|
'implot/implot_items.cpp'
|
||||||
|
#'implot/implot_demo.cpp',
|
||||||
|
)
|
||||||
|
|
||||||
|
libimplot = static_library('implot',
|
||||||
|
sources: implot_files,
|
||||||
|
include_directories: 'implot',
|
||||||
|
dependencies: [imgui])
|
||||||
|
implot = declare_dependency(link_with: libimplot,
|
||||||
|
include_directories: 'implot')
|
||||||
|
|
||||||
|
noc_ss = ss.source_set()
|
||||||
|
noc_ss.add(when: 'CONFIG_LINUX', if_true: [xemu_gtk, files('noc_file_dialog/noc_file_dialog_gtk.c')])
|
||||||
|
noc_ss.add(when: 'CONFIG_WIN32', if_true: files('noc_file_dialog/noc_file_dialog_win32.c'))
|
||||||
|
noc_ss.add(when: 'CONFIG_DARWIN', if_true: files('noc_file_dialog/noc_file_dialog_macos.m'))
|
||||||
|
noc_ss = noc_ss.apply(config_all, strict: false)
|
||||||
|
noclib = static_library('noc',
|
||||||
|
sources: noc_ss.sources(),
|
||||||
|
dependencies: noc_ss.dependencies(),
|
||||||
|
include_directories: 'noc_file_dialog')
|
||||||
|
noc = declare_dependency(include_directories: 'noc_file_dialog', link_with: noclib)
|
||||||
|
|
||||||
|
libstb_image = static_library('stb_image',
|
||||||
|
sources: 'stb_image/stb_image_impl.c')
|
||||||
|
stb_image = declare_dependency(include_directories: 'stb_image',
|
||||||
|
link_with: libstb_image)
|
||||||
|
|
||||||
|
fa = declare_dependency(include_directories: 'fa')
|
||||||
|
|
||||||
|
if cpu == 'x86_64'
|
||||||
|
libfpng_cpp_args = ['-DFPNG_NO_SSE=0', '-msse4.1', '-mpclmul']
|
||||||
|
else
|
||||||
|
libfpng_cpp_args = ['-DFPNG_NO_SSE=1']
|
||||||
|
endif
|
||||||
|
|
||||||
|
libfpng = static_library('fpng', sources: 'fpng/fpng.cpp', cpp_args: libfpng_cpp_args)
|
||||||
|
fpng = declare_dependency(include_directories: 'fpng', link_with: libfpng)
|
||||||
|
|
||||||
|
json = declare_dependency(include_directories: 'json')
|
||||||
|
httplib = declare_dependency(include_directories: 'httplib')
|
|
@ -20,6 +20,8 @@
|
||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
* IN THE SOFTWARE.
|
* IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
#ifndef NOC_FILE_DIALOG_H
|
||||||
|
#define NOC_FILE_DIALOG_H
|
||||||
|
|
||||||
/* A portable library to create open and save dialogs on linux, osx and
|
/* A portable library to create open and save dialogs on linux, osx and
|
||||||
* windows.
|
* windows.
|
||||||
|
@ -328,3 +330,4 @@ const char *noc_file_dialog_open(int flags,
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,2 @@
|
||||||
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
#include "stb_image.h"
|
|
@ -1,311 +0,0 @@
|
||||||
/*
|
|
||||||
* xemu User Interface Rendering Helpers
|
|
||||||
*
|
|
||||||
* Copyright (C) 2020-2021 Matt Borgerson
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <SDL.h>
|
|
||||||
#include <epoxy/gl.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <math.h>
|
|
||||||
|
|
||||||
#include "xemu-shaders.h"
|
|
||||||
#include "xemu-custom-widgets.h"
|
|
||||||
|
|
||||||
#include "data/controller_mask.png.h"
|
|
||||||
#include "data/logo_sdf.png.h"
|
|
||||||
|
|
||||||
static struct decal_shader *s = NULL;
|
|
||||||
static struct decal_shader *s_logo = NULL;
|
|
||||||
GLuint main_fb;
|
|
||||||
struct fbo *controller_fbo;
|
|
||||||
struct fbo *logo_fbo;
|
|
||||||
GLint vp[4];
|
|
||||||
GLuint g_ui_tex;
|
|
||||||
GLuint g_logo_tex;
|
|
||||||
|
|
||||||
struct rect {
|
|
||||||
int x, y, w, h;
|
|
||||||
};
|
|
||||||
|
|
||||||
const struct rect tex_items[] = {
|
|
||||||
{ 0, 148, 467, 364 }, // obj_controller
|
|
||||||
{ 0, 81, 67, 67 }, // obj_lstick
|
|
||||||
{ 0, 14, 67, 67 }, // obj_rstick
|
|
||||||
{ 67, 104, 68, 44 }, // obj_port_socket
|
|
||||||
{ 67, 76, 28, 28 }, // obj_port_lbl_1
|
|
||||||
{ 67, 48, 28, 28 }, // obj_port_lbl_2
|
|
||||||
{ 67, 20, 28, 28 }, // obj_port_lbl_3
|
|
||||||
{ 95, 76, 28, 28 }, // obj_port_lbl_4
|
|
||||||
};
|
|
||||||
|
|
||||||
enum tex_item_names {
|
|
||||||
obj_controller,
|
|
||||||
obj_lstick,
|
|
||||||
obj_rstick,
|
|
||||||
obj_port_socket,
|
|
||||||
obj_port_lbl_1,
|
|
||||||
obj_port_lbl_2,
|
|
||||||
obj_port_lbl_3,
|
|
||||||
obj_port_lbl_4,
|
|
||||||
};
|
|
||||||
|
|
||||||
void initialize_custom_ui_rendering(void)
|
|
||||||
{
|
|
||||||
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, (GLint*)&main_fb);
|
|
||||||
glGetIntegerv(GL_VIEWPORT, vp);
|
|
||||||
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
g_ui_tex = load_texture_from_memory(controller_mask_data, controller_mask_size);
|
|
||||||
s = create_decal_shader(SHADER_TYPE_MASK);
|
|
||||||
g_logo_tex = load_texture_from_memory(logo_sdf_data, logo_sdf_size);
|
|
||||||
s_logo = create_decal_shader(SHADER_TYPE_LOGO);
|
|
||||||
controller_fbo = create_fbo(512, 512);
|
|
||||||
logo_fbo = create_fbo(512, 512);
|
|
||||||
render_to_default_fb();
|
|
||||||
}
|
|
||||||
|
|
||||||
void render_meter(
|
|
||||||
struct decal_shader *s,
|
|
||||||
float x, float y, float width, float height, float p,
|
|
||||||
uint32_t color_bg, uint32_t color_fg)
|
|
||||||
{
|
|
||||||
render_decal(s, x, y, width, height,0, 0, 1, 1, 0, 0, color_bg);
|
|
||||||
render_decal(s, x, y, width*p, height ,0, 0, 1, 1, 0, 0, color_fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void render_controller(float frame_x, float frame_y, uint32_t primary_color, uint32_t secondary_color, ControllerState *state)
|
|
||||||
{
|
|
||||||
// Location within the controller texture of masked button locations,
|
|
||||||
// relative to the origin of the controller
|
|
||||||
const struct rect jewel = { 177, 172, 113, 118 };
|
|
||||||
const struct rect lstick_ctr = { 93, 246, 0, 0 };
|
|
||||||
const struct rect rstick_ctr = { 342, 148, 0, 0 };
|
|
||||||
const struct rect buttons[12] = {
|
|
||||||
{ 367, 187, 30, 38 }, // A
|
|
||||||
{ 368, 229, 30, 38 }, // B
|
|
||||||
{ 330, 204, 30, 38 }, // X
|
|
||||||
{ 331, 247, 30, 38 }, // Y
|
|
||||||
{ 82, 121, 31, 47 }, // D-Left
|
|
||||||
{ 104, 160, 44, 25 }, // D-Up
|
|
||||||
{ 141, 121, 31, 47 }, // D-Right
|
|
||||||
{ 104, 105, 44, 25 }, // D-Down
|
|
||||||
{ 187, 94, 34, 24 }, // Back
|
|
||||||
{ 246, 94, 36, 26 }, // Start
|
|
||||||
{ 348, 288, 30, 38 }, // White
|
|
||||||
{ 386, 268, 30, 38 }, // Black
|
|
||||||
};
|
|
||||||
|
|
||||||
uint8_t alpha = 0;
|
|
||||||
uint32_t now = SDL_GetTicks();
|
|
||||||
float t;
|
|
||||||
|
|
||||||
glUseProgram(s->prog);
|
|
||||||
glBindVertexArray(s->vao);
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, g_ui_tex);
|
|
||||||
|
|
||||||
// Add a 5 pixel space around the controller so we can wiggle the controller
|
|
||||||
// around to visualize rumble in action
|
|
||||||
frame_x += 5;
|
|
||||||
frame_y += 5;
|
|
||||||
float original_frame_x = frame_x;
|
|
||||||
float original_frame_y = frame_y;
|
|
||||||
|
|
||||||
// Floating point versions that will get scaled
|
|
||||||
float rumble_l = 0;
|
|
||||||
float rumble_r = 0;
|
|
||||||
|
|
||||||
glBlendEquation(GL_FUNC_ADD);
|
|
||||||
glBlendFunc(GL_ONE, GL_ZERO);
|
|
||||||
|
|
||||||
uint32_t jewel_color = secondary_color;
|
|
||||||
|
|
||||||
// Check to see if the guide button is pressed
|
|
||||||
const uint32_t animate_guide_button_duration = 2000;
|
|
||||||
if (state->buttons & CONTROLLER_BUTTON_GUIDE) {
|
|
||||||
state->animate_guide_button_end = now + animate_guide_button_duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (now < state->animate_guide_button_end) {
|
|
||||||
t = 1.0f - (float)(state->animate_guide_button_end-now)/(float)animate_guide_button_duration;
|
|
||||||
float sin_wav = (1-sin(M_PI * t / 2.0f));
|
|
||||||
|
|
||||||
// Animate guide button by highlighting logo jewel and fading out over time
|
|
||||||
alpha = sin_wav * 255.0f;
|
|
||||||
jewel_color = primary_color + alpha;
|
|
||||||
|
|
||||||
// Add a little extra flare: wiggle the frame around while we rumble
|
|
||||||
frame_x += ((float)(rand() % 5)-2.5) * (1-t);
|
|
||||||
frame_y += ((float)(rand() % 5)-2.5) * (1-t);
|
|
||||||
rumble_l = rumble_r = sin_wav;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render controller texture
|
|
||||||
render_decal(s,
|
|
||||||
frame_x+0, frame_y+0, tex_items[obj_controller].w, tex_items[obj_controller].h,
|
|
||||||
tex_items[obj_controller].x, tex_items[obj_controller].y, tex_items[obj_controller].w, tex_items[obj_controller].h,
|
|
||||||
primary_color, secondary_color, 0);
|
|
||||||
|
|
||||||
glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ONE); // Blend with controller cutouts
|
|
||||||
render_decal(s, frame_x+jewel.x, frame_y+jewel.y, jewel.w, jewel.h, 0, 0, 1, 1, 0, 0, jewel_color);
|
|
||||||
|
|
||||||
// The controller has alpha cutouts where the buttons are. Draw a surface
|
|
||||||
// behind the buttons if they are activated
|
|
||||||
for (int i = 0; i < 12; i++) {
|
|
||||||
bool enabled = !!(state->buttons & (1 << i));
|
|
||||||
if (!enabled) continue;
|
|
||||||
render_decal(s,
|
|
||||||
frame_x+buttons[i].x, frame_y+buttons[i].y,
|
|
||||||
buttons[i].w, buttons[i].h,
|
|
||||||
0, 0, 1, 1,
|
|
||||||
0, 0, (enabled ? primary_color : secondary_color)+0xff);
|
|
||||||
}
|
|
||||||
|
|
||||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Blend with controller
|
|
||||||
|
|
||||||
// Render left thumbstick
|
|
||||||
float w = tex_items[obj_lstick].w;
|
|
||||||
float h = tex_items[obj_lstick].h;
|
|
||||||
float c_x = frame_x+lstick_ctr.x;
|
|
||||||
float c_y = frame_y+lstick_ctr.y;
|
|
||||||
float lstick_x = (float)state->axis[CONTROLLER_AXIS_LSTICK_X]/32768.0;
|
|
||||||
float lstick_y = (float)state->axis[CONTROLLER_AXIS_LSTICK_Y]/32768.0;
|
|
||||||
render_decal(s,
|
|
||||||
(int)(c_x-w/2.0f+10.0f*lstick_x),
|
|
||||||
(int)(c_y-h/2.0f+10.0f*lstick_y),
|
|
||||||
w, h,
|
|
||||||
tex_items[obj_lstick].x, tex_items[obj_lstick].y, w, h,
|
|
||||||
(state->buttons & CONTROLLER_BUTTON_LSTICK) ? secondary_color : primary_color,
|
|
||||||
(state->buttons & CONTROLLER_BUTTON_LSTICK) ? primary_color : secondary_color,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
// Render right thumbstick
|
|
||||||
w = tex_items[obj_rstick].w;
|
|
||||||
h = tex_items[obj_rstick].h;
|
|
||||||
c_x = frame_x+rstick_ctr.x;
|
|
||||||
c_y = frame_y+rstick_ctr.y;
|
|
||||||
float rstick_x = (float)state->axis[CONTROLLER_AXIS_RSTICK_X]/32768.0;
|
|
||||||
float rstick_y = (float)state->axis[CONTROLLER_AXIS_RSTICK_Y]/32768.0;
|
|
||||||
render_decal(s,
|
|
||||||
(int)(c_x-w/2.0f+10.0f*rstick_x),
|
|
||||||
(int)(c_y-h/2.0f+10.0f*rstick_y),
|
|
||||||
w, h,
|
|
||||||
tex_items[obj_rstick].x, tex_items[obj_rstick].y, w, h,
|
|
||||||
(state->buttons & CONTROLLER_BUTTON_RSTICK) ? secondary_color : primary_color,
|
|
||||||
(state->buttons & CONTROLLER_BUTTON_RSTICK) ? primary_color : secondary_color,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
glBlendFunc(GL_ONE, GL_ZERO); // Don't blend, just overwrite values in buffer
|
|
||||||
|
|
||||||
// Render trigger bars
|
|
||||||
float ltrig = state->axis[CONTROLLER_AXIS_LTRIG] / 32767.0;
|
|
||||||
float rtrig = state->axis[CONTROLLER_AXIS_RTRIG] / 32767.0;
|
|
||||||
const uint32_t animate_trigger_duration = 1000;
|
|
||||||
if ((ltrig > 0) || (rtrig > 0)) {
|
|
||||||
state->animate_trigger_end = now + animate_trigger_duration;
|
|
||||||
rumble_l = fmax(rumble_l, ltrig);
|
|
||||||
rumble_r = fmax(rumble_r, rtrig);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Animate trigger alpha down after a period of inactivity
|
|
||||||
alpha = 0x80;
|
|
||||||
if (state->animate_trigger_end > now) {
|
|
||||||
t = 1.0f - (float)(state->animate_trigger_end-now)/(float)animate_trigger_duration;
|
|
||||||
float sin_wav = (1-sin(M_PI * t / 2.0f));
|
|
||||||
alpha += fmin(sin_wav * 0x40, 0x80);
|
|
||||||
}
|
|
||||||
|
|
||||||
render_meter(s,
|
|
||||||
original_frame_x+10,
|
|
||||||
original_frame_y+tex_items[obj_controller].h+20,
|
|
||||||
150, 5,
|
|
||||||
ltrig,
|
|
||||||
primary_color + alpha,
|
|
||||||
primary_color + 0xff);
|
|
||||||
render_meter(s,
|
|
||||||
original_frame_x+tex_items[obj_controller].w-160,
|
|
||||||
original_frame_y+tex_items[obj_controller].h+20,
|
|
||||||
150, 5,
|
|
||||||
rtrig,
|
|
||||||
primary_color + alpha,
|
|
||||||
primary_color + 0xff);
|
|
||||||
|
|
||||||
// Apply rumble updates
|
|
||||||
state->rumble_l = (int)(rumble_l * (float)0xffff);
|
|
||||||
state->rumble_r = (int)(rumble_r * (float)0xffff);
|
|
||||||
xemu_input_update_rumble(state);
|
|
||||||
|
|
||||||
glBindVertexArray(0);
|
|
||||||
glUseProgram(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void render_controller_port(float frame_x, float frame_y, int i, uint32_t port_color)
|
|
||||||
{
|
|
||||||
glUseProgram(s->prog);
|
|
||||||
glBindVertexArray(s->vao);
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, g_ui_tex);
|
|
||||||
|
|
||||||
glBlendFunc(GL_ONE, GL_ZERO);
|
|
||||||
|
|
||||||
// Render port socket
|
|
||||||
render_decal(s,
|
|
||||||
frame_x, frame_y,
|
|
||||||
tex_items[obj_port_socket].w, tex_items[obj_port_socket].h,
|
|
||||||
tex_items[obj_port_socket].x, tex_items[obj_port_socket].y,
|
|
||||||
tex_items[obj_port_socket].w, tex_items[obj_port_socket].h,
|
|
||||||
port_color, port_color, 0
|
|
||||||
);
|
|
||||||
|
|
||||||
frame_x += (tex_items[obj_port_socket].w-tex_items[obj_port_lbl_1].w)/2;
|
|
||||||
frame_y += tex_items[obj_port_socket].h + 8;
|
|
||||||
|
|
||||||
// Render port label
|
|
||||||
render_decal(s,
|
|
||||||
frame_x, frame_y,
|
|
||||||
tex_items[obj_port_lbl_1+i].w, tex_items[obj_port_lbl_1+i].h,
|
|
||||||
tex_items[obj_port_lbl_1+i].x, tex_items[obj_port_lbl_1+i].y,
|
|
||||||
tex_items[obj_port_lbl_1+i].w, tex_items[obj_port_lbl_1+i].h,
|
|
||||||
port_color, port_color, 0
|
|
||||||
);
|
|
||||||
|
|
||||||
glBindVertexArray(0);
|
|
||||||
glUseProgram(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void render_logo(uint32_t time, uint32_t primary_color, uint32_t secondary_color, uint32_t fill_color)
|
|
||||||
{
|
|
||||||
s_logo->time = time;
|
|
||||||
glUseProgram(s_logo->prog);
|
|
||||||
glBindVertexArray(s->vao);
|
|
||||||
glBlendFunc(GL_ONE, GL_ZERO);
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, g_logo_tex);
|
|
||||||
render_decal(
|
|
||||||
s_logo,
|
|
||||||
0, 0, 512, 512,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
128,
|
|
||||||
128,
|
|
||||||
primary_color, secondary_color, fill_color
|
|
||||||
);
|
|
||||||
glBindVertexArray(0);
|
|
||||||
glUseProgram(0);
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
/*
|
|
||||||
* xemu User Interface Rendering Helpers
|
|
||||||
*
|
|
||||||
* Copyright (C) 2020-2021 Matt Borgerson
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef XEMU_CUSTOM_WIDGETS
|
|
||||||
#define XEMU_CUSTOM_WIDGETS
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include "xemu-input.h"
|
|
||||||
#include "xemu-shaders.h"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// FIXME: Cleanup
|
|
||||||
extern struct fbo *controller_fbo;
|
|
||||||
extern struct fbo *logo_fbo;
|
|
||||||
|
|
||||||
void initialize_custom_ui_rendering(void);
|
|
||||||
void render_meter(struct decal_shader *s, float x, float y, float width, float height, float p, uint32_t color_bg, uint32_t color_fg);
|
|
||||||
void render_controller(float frame_x, float frame_y, uint32_t primary_color, uint32_t secondary_color, ControllerState *state);
|
|
||||||
void render_controller_port(float frame_x, float frame_y, int i, uint32_t port_color);
|
|
||||||
void render_logo(uint32_t time, uint32_t primary_color, uint32_t secondary_color, uint32_t fill_color);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
2412
ui/xemu-hud.cc
2412
ui/xemu-hud.cc
File diff suppressed because it is too large
Load Diff
|
@ -96,7 +96,9 @@ static const char **port_index_to_settings_key_map[] = {
|
||||||
|
|
||||||
void xemu_input_init(void)
|
void xemu_input_init(void)
|
||||||
{
|
{
|
||||||
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
if (g_config.input.background_input_capture) {
|
||||||
|
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
||||||
|
}
|
||||||
|
|
||||||
if (SDL_Init(SDL_INIT_GAMECONTROLLER) < 0) {
|
if (SDL_Init(SDL_INIT_GAMECONTROLLER) < 0) {
|
||||||
fprintf(stderr, "Failed to initialize SDL gamecontroller subsystem: %s\n", SDL_GetError());
|
fprintf(stderr, "Failed to initialize SDL gamecontroller subsystem: %s\n", SDL_GetError());
|
||||||
|
@ -191,25 +193,42 @@ void xemu_input_process_sdl_events(const SDL_Event *event)
|
||||||
// upside in this case is that a person can use the same GUID on all
|
// upside in this case is that a person can use the same GUID on all
|
||||||
// ports and just needs to bind to the receiver and never needs to hit
|
// ports and just needs to bind to the receiver and never needs to hit
|
||||||
// this dialog.
|
// this dialog.
|
||||||
|
|
||||||
|
|
||||||
|
// Attempt to re-bind to port previously bound to
|
||||||
int port = 0;
|
int port = 0;
|
||||||
while (1) {
|
bool did_bind = false;
|
||||||
|
while (!did_bind) {
|
||||||
port = xemu_input_get_controller_default_bind_port(new_con, port);
|
port = xemu_input_get_controller_default_bind_port(new_con, port);
|
||||||
if (port < 0) {
|
if (port < 0) {
|
||||||
// No (additional) default mappings
|
// No (additional) default mappings
|
||||||
break;
|
break;
|
||||||
}
|
} else if (!xemu_input_get_bound(port)) {
|
||||||
if (xemu_input_get_bound(port) != NULL) {
|
xemu_input_bind(port, new_con, 0);
|
||||||
// Something already bound here, try again for another port
|
did_bind = true;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// Try again for another port
|
||||||
port++;
|
port++;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
xemu_input_bind(port, new_con, 0);
|
}
|
||||||
|
|
||||||
|
// Try to bind to any open port, and if so remember the binding
|
||||||
|
if (!did_bind && g_config.input.auto_bind) {
|
||||||
|
for (port = 0; port < 4; port++) {
|
||||||
|
if (!xemu_input_get_bound(port)) {
|
||||||
|
xemu_input_bind(port, new_con, 1);
|
||||||
|
did_bind = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (did_bind) {
|
||||||
char buf[128];
|
char buf[128];
|
||||||
snprintf(buf, sizeof(buf), "Connected '%s' to port %d", new_con->name, port+1);
|
snprintf(buf, sizeof(buf), "Connected '%s' to port %d", new_con->name, port+1);
|
||||||
xemu_queue_notification(buf);
|
xemu_queue_notification(buf);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
|
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||||
DPRINTF("Controller Removed: %d\n", event->cdevice.which);
|
DPRINTF("Controller Removed: %d\n", event->cdevice.which);
|
||||||
int handled = 0;
|
int handled = 0;
|
||||||
|
|
|
@ -33,6 +33,8 @@
|
||||||
#include "qemu/config-file.h"
|
#include "qemu/config-file.h"
|
||||||
#include "net/net.h"
|
#include "net/net.h"
|
||||||
#include "net/hub.h"
|
#include "net/hub.h"
|
||||||
|
#include "net/slirp.h"
|
||||||
|
#include <libslirp.h>
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
#include <pcap/pcap.h>
|
#include <pcap/pcap.h>
|
||||||
#endif
|
#endif
|
||||||
|
@ -41,6 +43,8 @@
|
||||||
static const char *id = "xemu-netdev";
|
static const char *id = "xemu-netdev";
|
||||||
static const char *id_hubport = "xemu-netdev-hubport";
|
static const char *id_hubport = "xemu-netdev-hubport";
|
||||||
|
|
||||||
|
void *slirp_get_state_from_netdev(const char *id);
|
||||||
|
|
||||||
void xemu_net_enable(void)
|
void xemu_net_enable(void)
|
||||||
{
|
{
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
|
@ -102,6 +106,38 @@ void xemu_net_enable(void)
|
||||||
// error_propagate(errp, local_err);
|
// error_propagate(errp, local_err);
|
||||||
xemu_queue_error_message(error_get_pretty(local_err));
|
xemu_queue_error_message(error_get_pretty(local_err));
|
||||||
error_report_err(local_err);
|
error_report_err(local_err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_config.net.backend == CONFIG_NET_BACKEND_NAT) {
|
||||||
|
void *s = slirp_get_state_from_netdev(id);
|
||||||
|
assert(s != NULL);
|
||||||
|
|
||||||
|
struct in_addr host_addr = { .s_addr = INADDR_ANY };
|
||||||
|
struct in_addr guest_addr = { .s_addr = 0 };
|
||||||
|
inet_aton("10.0.2.15", &guest_addr);
|
||||||
|
|
||||||
|
for (int i = 0; i < g_config.net.nat.forward_ports_count; i++) {
|
||||||
|
bool is_udp = g_config.net.nat.forward_ports[i].protocol ==
|
||||||
|
CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL_UDP;
|
||||||
|
int host_port = g_config.net.nat.forward_ports[i].host;
|
||||||
|
int guest_port = g_config.net.nat.forward_ports[i].guest;
|
||||||
|
|
||||||
|
if (slirp_add_hostfwd(s, is_udp, host_addr, host_port, guest_addr,
|
||||||
|
guest_port) < 0) {
|
||||||
|
error_setg(&local_err,
|
||||||
|
"Could not set host forwarding rule "
|
||||||
|
"%d -> %d (%s)\n",
|
||||||
|
host_port, guest_port, is_udp ? "udp" : "tcp");
|
||||||
|
xemu_queue_error_message(error_get_pretty(local_err));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (local_err) {
|
||||||
|
xemu_net_disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
g_config.net.enable = true;
|
g_config.net.enable = true;
|
||||||
|
@ -124,13 +160,25 @@ static void remove_netdev(const char *name)
|
||||||
// error_setg(errp, "Device '%s' is not a netdev", name);
|
// error_setg(errp, "Device '%s' is not a netdev", name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_opts_del(opts);
|
qemu_opts_del(opts);
|
||||||
qemu_del_net_client(nc);
|
qemu_del_net_client(nc);
|
||||||
}
|
}
|
||||||
|
|
||||||
void xemu_net_disable(void)
|
void xemu_net_disable(void)
|
||||||
{
|
{
|
||||||
|
if (g_config.net.backend == CONFIG_NET_BACKEND_NAT) {
|
||||||
|
void *s = slirp_get_state_from_netdev(id);
|
||||||
|
assert(s != NULL);
|
||||||
|
struct in_addr host_addr = { .s_addr = INADDR_ANY };
|
||||||
|
for (int i = 0; i < g_config.net.nat.forward_ports_count; i++) {
|
||||||
|
slirp_remove_hostfwd(s,
|
||||||
|
g_config.net.nat.forward_ports[i].protocol ==
|
||||||
|
CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL_UDP,
|
||||||
|
host_addr,
|
||||||
|
g_config.net.nat.forward_ports[i].host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
remove_netdev(id);
|
remove_netdev(id);
|
||||||
remove_netdev(id_hubport);
|
remove_netdev(id_hubport);
|
||||||
g_config.net.enable = false;
|
g_config.net.enable = false;
|
||||||
|
|
|
@ -25,9 +25,46 @@ extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const char *xemu_get_os_info(void);
|
const char *xemu_get_os_info(void);
|
||||||
const char *xemu_get_cpu_info(void);
|
|
||||||
void xemu_open_web_browser(const char *url);
|
void xemu_open_web_browser(const char *url);
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
#ifdef CONFIG_CPUID_H
|
||||||
|
#include <cpuid.h>
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static inline const char *xemu_get_os_platform(void)
|
||||||
|
{
|
||||||
|
const char *platform_name;
|
||||||
|
|
||||||
|
#if defined(__linux__)
|
||||||
|
platform_name = "Linux";
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
platform_name = "Windows";
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
platform_name = "macOS";
|
||||||
|
#else
|
||||||
|
platform_name = "Unknown";
|
||||||
|
#endif
|
||||||
|
return platform_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline const char *xemu_get_cpu_info(void)
|
||||||
|
{
|
||||||
|
const char *cpu_info = "";
|
||||||
|
#ifdef CONFIG_CPUID_H
|
||||||
|
static uint32_t brand[12];
|
||||||
|
if (__get_cpuid_max(0x80000004, NULL)) {
|
||||||
|
__get_cpuid(0x80000002, brand+0x0, brand+0x1, brand+0x2, brand+0x3);
|
||||||
|
__get_cpuid(0x80000003, brand+0x4, brand+0x5, brand+0x6, brand+0x7);
|
||||||
|
__get_cpuid(0x80000004, brand+0x8, brand+0x9, brand+0xa, brand+0xb);
|
||||||
|
}
|
||||||
|
cpu_info = (const char *)brand;
|
||||||
|
#endif
|
||||||
|
// FIXME: Support other architectures (e.g. ARM)
|
||||||
|
return cpu_info;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -187,3 +187,31 @@ void xemu_settings_save(void)
|
||||||
fprintf(fd, "%s", config_tree.generate_delta_toml().c_str());
|
fprintf(fd, "%s", config_tree.generate_delta_toml().c_str());
|
||||||
fclose(fd);
|
fclose(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void add_net_nat_forward_ports(int host, int guest, CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL protocol)
|
||||||
|
{
|
||||||
|
// FIXME: - Realloc the arrays instead of free/alloc
|
||||||
|
// - Don't need to copy as much
|
||||||
|
auto cnode = config_tree.child("net")
|
||||||
|
->child("nat")
|
||||||
|
->child("forward_ports");
|
||||||
|
cnode->update_from_struct(&g_config);
|
||||||
|
cnode->children.push_back(*cnode->array_item_type);
|
||||||
|
auto &e = cnode->children.back();
|
||||||
|
e.child("host")->set_integer(host);
|
||||||
|
e.child("guest")->set_integer(guest);
|
||||||
|
e.child("protocol")->set_enum_by_index(protocol);
|
||||||
|
cnode->free_allocations(&g_config);
|
||||||
|
cnode->store_to_struct(&g_config);
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_net_nat_forward_ports(unsigned int index)
|
||||||
|
{
|
||||||
|
auto cnode = config_tree.child("net")
|
||||||
|
->child("nat")
|
||||||
|
->child("forward_ports");
|
||||||
|
cnode->update_from_struct(&g_config);
|
||||||
|
cnode->children.erase(cnode->children.begin()+index);
|
||||||
|
cnode->free_allocations(&g_config);
|
||||||
|
cnode->store_to_struct(&g_config);
|
||||||
|
}
|
||||||
|
|
|
@ -59,6 +59,9 @@ static inline void xemu_settings_set_string(const char **str, const char *new_st
|
||||||
*str = strdup(new_str);
|
*str = strdup(new_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void add_net_nat_forward_ports(int host, int guest, CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL protocol);
|
||||||
|
void remove_net_nat_forward_ports(unsigned int index);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,371 +0,0 @@
|
||||||
/*
|
|
||||||
* xemu User Interface Rendering Helpers
|
|
||||||
*
|
|
||||||
* Copyright (C) 2020-2021 Matt Borgerson
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <SDL.h>
|
|
||||||
#include <epoxy/gl.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <math.h>
|
|
||||||
#include "xemu-shaders.h"
|
|
||||||
#include "ui/shader/xemu-logo-frag.h"
|
|
||||||
|
|
||||||
#define STB_IMAGE_IMPLEMENTATION
|
|
||||||
#include "stb_image.h"
|
|
||||||
|
|
||||||
GLuint compile_shader(GLenum type, const char *src)
|
|
||||||
{
|
|
||||||
GLint status;
|
|
||||||
char err_buf[512];
|
|
||||||
GLuint shader = glCreateShader(type);
|
|
||||||
if (shader == 0) {
|
|
||||||
fprintf(stderr, "ERROR: Failed to create shader\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
glShaderSource(shader, 1, &src, NULL);
|
|
||||||
glCompileShader(shader);
|
|
||||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
|
|
||||||
if (status != GL_TRUE) {
|
|
||||||
glGetShaderInfoLog(shader, sizeof(err_buf), NULL, err_buf);
|
|
||||||
fprintf(stderr, "ERROR: Shader compilation failed!\n\n");
|
|
||||||
fprintf(stderr, "[Shader Info Log]\n");
|
|
||||||
fprintf(stderr, "%s\n", err_buf);
|
|
||||||
fprintf(stderr, "[Shader Source]\n");
|
|
||||||
fprintf(stderr, "%s\n", src);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct decal_shader *create_decal_shader(enum SHADER_TYPE type)
|
|
||||||
{
|
|
||||||
// Allocate shader wrapper object
|
|
||||||
struct decal_shader *s = (struct decal_shader *)malloc(sizeof(struct decal_shader));
|
|
||||||
assert(s != NULL);
|
|
||||||
s->flip = 0;
|
|
||||||
s->scale = 1.4;
|
|
||||||
s->smoothing = 1.0;
|
|
||||||
s->outline_dist = 1.0;
|
|
||||||
s->time = 0;
|
|
||||||
|
|
||||||
const char *vert_src =
|
|
||||||
"#version 150 core\n"
|
|
||||||
"uniform bool in_FlipY;\n"
|
|
||||||
"uniform vec4 in_ScaleOffset;\n"
|
|
||||||
"uniform vec4 in_TexScaleOffset;\n"
|
|
||||||
"in vec2 in_Position;\n"
|
|
||||||
"in vec2 in_Texcoord;\n"
|
|
||||||
"out vec2 Texcoord;\n"
|
|
||||||
"void main() {\n"
|
|
||||||
" vec2 t = in_Texcoord;\n"
|
|
||||||
" if (in_FlipY) t.y = 1-t.y;\n"
|
|
||||||
" Texcoord = t*in_TexScaleOffset.xy + in_TexScaleOffset.zw;\n"
|
|
||||||
" gl_Position = vec4(in_Position*in_ScaleOffset.xy+in_ScaleOffset.zw, 0.0, 1.0);\n"
|
|
||||||
"}\n";
|
|
||||||
GLuint vert = compile_shader(GL_VERTEX_SHADER, vert_src);
|
|
||||||
assert(vert != 0);
|
|
||||||
|
|
||||||
const char *image_frag_src =
|
|
||||||
"#version 150 core\n"
|
|
||||||
"uniform sampler2D tex;\n"
|
|
||||||
"in vec2 Texcoord;\n"
|
|
||||||
"out vec4 out_Color;\n"
|
|
||||||
"void main() {\n"
|
|
||||||
" out_Color.rgba = texture(tex, Texcoord);\n"
|
|
||||||
"}\n";
|
|
||||||
|
|
||||||
const char *image_gamma_frag_src =
|
|
||||||
"#version 400 core\n"
|
|
||||||
"uniform sampler2D tex;\n"
|
|
||||||
"uniform uint palette[256];\n"
|
|
||||||
"float gamma_ch(int ch, float col)\n"
|
|
||||||
"{\n"
|
|
||||||
" return float(bitfieldExtract(palette[uint(col * 255.0)], ch*8, 8)) / 255.0;\n"
|
|
||||||
"}\n"
|
|
||||||
"\n"
|
|
||||||
"vec4 gamma(vec4 col)\n"
|
|
||||||
"{\n"
|
|
||||||
" return vec4(gamma_ch(0, col.r), gamma_ch(1, col.g), gamma_ch(2, col.b), col.a);\n"
|
|
||||||
"}\n"
|
|
||||||
"in vec2 Texcoord;\n"
|
|
||||||
"out vec4 out_Color;\n"
|
|
||||||
"void main() {\n"
|
|
||||||
" out_Color.rgba = gamma(texture(tex, Texcoord));\n"
|
|
||||||
"}\n";
|
|
||||||
|
|
||||||
// Simple 2-color decal shader
|
|
||||||
// - in_ColorFill is first pass
|
|
||||||
// - Red channel of the texture is used as primary color, mixed with 1-Red for
|
|
||||||
// secondary color.
|
|
||||||
// - Blue is a lazy alpha removal for now
|
|
||||||
// - Alpha channel passed through
|
|
||||||
const char *mask_frag_src =
|
|
||||||
"#version 150 core\n"
|
|
||||||
"uniform sampler2D tex;\n"
|
|
||||||
"uniform vec4 in_ColorPrimary;\n"
|
|
||||||
"uniform vec4 in_ColorSecondary;\n"
|
|
||||||
"uniform vec4 in_ColorFill;\n"
|
|
||||||
"in vec2 Texcoord;\n"
|
|
||||||
"out vec4 out_Color;\n"
|
|
||||||
"void main() {\n"
|
|
||||||
" vec4 t = texture(tex, Texcoord);\n"
|
|
||||||
" out_Color.rgba = in_ColorFill.rgba;\n"
|
|
||||||
" out_Color.rgb += mix(in_ColorSecondary.rgb, in_ColorPrimary.rgb, t.r);\n"
|
|
||||||
" out_Color.a += t.a - t.b;\n"
|
|
||||||
"}\n";
|
|
||||||
|
|
||||||
const char *frag_src = NULL;
|
|
||||||
if (type == SHADER_TYPE_MASK) {
|
|
||||||
frag_src = mask_frag_src;
|
|
||||||
} else if (type == SHADER_TYPE_BLIT) {
|
|
||||||
frag_src = image_frag_src;
|
|
||||||
} else if (type == SHADER_TYPE_BLIT_GAMMA) {
|
|
||||||
frag_src = image_gamma_frag_src;
|
|
||||||
} else if (type == SHADER_TYPE_LOGO) {
|
|
||||||
frag_src = xemu_logo_frag_src;
|
|
||||||
} else {
|
|
||||||
assert(0);
|
|
||||||
}
|
|
||||||
GLuint frag = compile_shader(GL_FRAGMENT_SHADER, frag_src);
|
|
||||||
assert(frag != 0);
|
|
||||||
|
|
||||||
// Link vertex and fragment shaders
|
|
||||||
s->prog = glCreateProgram();
|
|
||||||
glAttachShader(s->prog, vert);
|
|
||||||
glAttachShader(s->prog, frag);
|
|
||||||
glBindFragDataLocation(s->prog, 0, "out_Color");
|
|
||||||
glLinkProgram(s->prog);
|
|
||||||
glUseProgram(s->prog);
|
|
||||||
|
|
||||||
// Flag shaders for deletion when program is deleted
|
|
||||||
glDeleteShader(vert);
|
|
||||||
glDeleteShader(frag);
|
|
||||||
|
|
||||||
s->FlipY_loc = glGetUniformLocation(s->prog, "in_FlipY");
|
|
||||||
s->ScaleOffset_loc = glGetUniformLocation(s->prog, "in_ScaleOffset");
|
|
||||||
s->TexScaleOffset_loc = glGetUniformLocation(s->prog, "in_TexScaleOffset");
|
|
||||||
s->tex_loc = glGetUniformLocation(s->prog, "tex");
|
|
||||||
s->ColorPrimary_loc = glGetUniformLocation(s->prog, "in_ColorPrimary");
|
|
||||||
s->ColorSecondary_loc = glGetUniformLocation(s->prog, "in_ColorSecondary");
|
|
||||||
s->ColorFill_loc = glGetUniformLocation(s->prog, "in_ColorFill");
|
|
||||||
s->time_loc = glGetUniformLocation(s->prog, "iTime");
|
|
||||||
s->scale_loc = glGetUniformLocation(s->prog, "scale");
|
|
||||||
for (int i = 0; i < 256; i++) {
|
|
||||||
char name[64];
|
|
||||||
snprintf(name, sizeof(name), "palette[%d]", i);
|
|
||||||
s->palette_loc[i] = glGetUniformLocation(s->prog, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a vertex array object
|
|
||||||
glGenVertexArrays(1, &s->vao);
|
|
||||||
glBindVertexArray(s->vao);
|
|
||||||
|
|
||||||
// Populate vertex buffer
|
|
||||||
glGenBuffers(1, &s->vbo);
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, s->vbo);
|
|
||||||
const GLfloat verts[6][4] = {
|
|
||||||
// x y s t
|
|
||||||
{ -1.0f, -1.0f, 0.0f, 0.0f }, // BL
|
|
||||||
{ -1.0f, 1.0f, 0.0f, 1.0f }, // TL
|
|
||||||
{ 1.0f, 1.0f, 1.0f, 1.0f }, // TR
|
|
||||||
{ 1.0f, -1.0f, 1.0f, 0.0f }, // BR
|
|
||||||
};
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_COPY);
|
|
||||||
|
|
||||||
// Populate element buffer
|
|
||||||
glGenBuffers(1, &s->ebo);
|
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, s->ebo);
|
|
||||||
const GLint indicies[] = { 0, 1, 2, 3 };
|
|
||||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indicies), indicies, GL_STATIC_DRAW);
|
|
||||||
|
|
||||||
// Bind vertex position attribute
|
|
||||||
GLint pos_attr_loc = glGetAttribLocation(s->prog, "in_Position");
|
|
||||||
glVertexAttribPointer(pos_attr_loc, 2, GL_FLOAT, GL_FALSE, 4*sizeof(GLfloat), (void*)0);
|
|
||||||
glEnableVertexAttribArray(pos_attr_loc);
|
|
||||||
|
|
||||||
// Bind vertex texture coordinate attribute
|
|
||||||
GLint tex_attr_loc = glGetAttribLocation(s->prog, "in_Texcoord");
|
|
||||||
if (tex_attr_loc >= 0) {
|
|
||||||
glVertexAttribPointer(tex_attr_loc, 2, GL_FLOAT, GL_FALSE, 4*sizeof(GLfloat), (void*)(2*sizeof(GLfloat)));
|
|
||||||
glEnableVertexAttribArray(tex_attr_loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
static GLuint load_texture(unsigned char *data, int width, int height, int channels)
|
|
||||||
{
|
|
||||||
GLuint tex;
|
|
||||||
glGenTextures(1, &tex);
|
|
||||||
assert(tex != 0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, tex);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
|
|
||||||
return tex;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint load_texture_from_file(const char *name)
|
|
||||||
{
|
|
||||||
// Flip vertically so textures are loaded according to GL convention.
|
|
||||||
stbi_set_flip_vertically_on_load(1);
|
|
||||||
|
|
||||||
// Read file into memory
|
|
||||||
int width, height, channels = 0;
|
|
||||||
unsigned char *data = stbi_load(name, &width, &height, &channels, 4);
|
|
||||||
assert(data != NULL);
|
|
||||||
|
|
||||||
GLuint tex = load_texture(data, width, height, channels);
|
|
||||||
stbi_image_free(data);
|
|
||||||
|
|
||||||
return tex;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint load_texture_from_memory(const unsigned char *buf, unsigned int size)
|
|
||||||
{
|
|
||||||
// Flip vertically so textures are loaded according to GL convention.
|
|
||||||
stbi_set_flip_vertically_on_load(1);
|
|
||||||
|
|
||||||
int width, height, channels = 0;
|
|
||||||
unsigned char *data = stbi_load_from_memory(buf, size, &width, &height, &channels, 4);
|
|
||||||
assert(data != NULL);
|
|
||||||
|
|
||||||
GLuint tex = load_texture(data, width, height, channels);
|
|
||||||
stbi_image_free(data);
|
|
||||||
|
|
||||||
return tex;
|
|
||||||
}
|
|
||||||
|
|
||||||
void render_decal(
|
|
||||||
struct decal_shader *s,
|
|
||||||
float x, float y, float w, float h,
|
|
||||||
float tex_x, float tex_y, float tex_w, float tex_h,
|
|
||||||
uint32_t primary, uint32_t secondary, uint32_t fill
|
|
||||||
)
|
|
||||||
{
|
|
||||||
GLint vp[4];
|
|
||||||
glGetIntegerv(GL_VIEWPORT, vp);
|
|
||||||
float ww = vp[2], wh = vp[3];
|
|
||||||
|
|
||||||
x = (int)x;
|
|
||||||
y = (int)y;
|
|
||||||
w = (int)w;
|
|
||||||
h = (int)h;
|
|
||||||
tex_x = (int)tex_x;
|
|
||||||
tex_y = (int)tex_y;
|
|
||||||
tex_w = (int)tex_w;
|
|
||||||
tex_h = (int)tex_h;
|
|
||||||
|
|
||||||
int tw_i, th_i;
|
|
||||||
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &tw_i);
|
|
||||||
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &th_i);
|
|
||||||
float tw = tw_i, th = th_i;
|
|
||||||
|
|
||||||
#define COL(color, c) (float)(((color)>>((c)*8)) & 0xff)/255.0
|
|
||||||
glUniform1i(s->FlipY_loc, s->flip);
|
|
||||||
glUniform4f(s->ScaleOffset_loc, w/ww, h/wh, -1+((2*x+w)/ww), -1+((2*y+h)/wh));
|
|
||||||
glUniform4f(s->TexScaleOffset_loc, tex_w/tw, tex_h/th, tex_x/tw, tex_y/th);
|
|
||||||
glUniform1i(s->tex_loc, 0);
|
|
||||||
glUniform4f(s->ColorPrimary_loc, COL(primary, 3), COL(primary, 2), COL(primary, 1), COL(primary, 0));
|
|
||||||
glUniform4f(s->ColorSecondary_loc, COL(secondary, 3), COL(secondary, 2), COL(secondary, 1), COL(secondary, 0));
|
|
||||||
glUniform4f(s->ColorFill_loc, COL(fill, 3), COL(fill, 2), COL(fill, 1), COL(fill, 0));
|
|
||||||
if (s->time_loc >= 0) glUniform1f(s->time_loc, s->time/1000.0f);
|
|
||||||
if (s->scale_loc >= 0) glUniform1f(s->scale_loc, s->scale);
|
|
||||||
#undef COL
|
|
||||||
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void render_decal_image(
|
|
||||||
struct decal_shader *s,
|
|
||||||
float x, float y, float w, float h,
|
|
||||||
float tex_x, float tex_y, float tex_w, float tex_h
|
|
||||||
)
|
|
||||||
{
|
|
||||||
GLint vp[4];
|
|
||||||
glGetIntegerv(GL_VIEWPORT, vp);
|
|
||||||
float ww = vp[2], wh = vp[3];
|
|
||||||
|
|
||||||
int tw_i, th_i;
|
|
||||||
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &tw_i);
|
|
||||||
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &th_i);
|
|
||||||
float tw = tw_i, th = th_i;
|
|
||||||
|
|
||||||
glUniform1i(s->FlipY_loc, s->flip);
|
|
||||||
glUniform4f(s->ScaleOffset_loc, w/ww, h/wh, -1+((2*x+w)/ww), -1+((2*y+h)/wh));
|
|
||||||
glUniform4f(s->TexScaleOffset_loc, tex_w/tw, tex_h/th, tex_x/tw, tex_y/th);
|
|
||||||
glUniform1i(s->tex_loc, 0);
|
|
||||||
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct fbo *create_fbo(int width, int height)
|
|
||||||
{
|
|
||||||
struct fbo *fbo = (struct fbo *)malloc(sizeof(struct fbo));
|
|
||||||
assert(fbo != NULL);
|
|
||||||
|
|
||||||
fbo->w = width;
|
|
||||||
fbo->h = height;
|
|
||||||
|
|
||||||
// Allocate the texture
|
|
||||||
glGenTextures(1, &fbo->tex);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, fbo->tex);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fbo->w, fbo->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
|
||||||
|
|
||||||
// Allocate the framebuffer object
|
|
||||||
glGenFramebuffers(1, &fbo->fbo);
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo->fbo);
|
|
||||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo->tex, 0);
|
|
||||||
GLenum DrawBuffers[1] = {GL_COLOR_ATTACHMENT0};
|
|
||||||
glDrawBuffers(1, DrawBuffers);
|
|
||||||
|
|
||||||
return fbo;
|
|
||||||
}
|
|
||||||
|
|
||||||
static GLboolean m_blend;
|
|
||||||
|
|
||||||
void render_to_default_fb(void)
|
|
||||||
{
|
|
||||||
if (!m_blend) {
|
|
||||||
glDisable(GL_BLEND);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore default framebuffer, viewport, blending funciton
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, main_fb);
|
|
||||||
glViewport(vp[0], vp[1], vp[2], vp[3]);
|
|
||||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint render_to_fbo(struct fbo *fbo)
|
|
||||||
{
|
|
||||||
m_blend = glIsEnabled(GL_BLEND);
|
|
||||||
if (!m_blend) {
|
|
||||||
glEnable(GL_BLEND);
|
|
||||||
}
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo->fbo);
|
|
||||||
glViewport(0, 0, fbo->w, fbo->h);
|
|
||||||
glClearColor(0, 0, 0, 0);
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
|
||||||
return fbo->tex;
|
|
||||||
}
|
|
|
@ -1,103 +0,0 @@
|
||||||
/*
|
|
||||||
* xemu User Interface Rendering Helpers
|
|
||||||
*
|
|
||||||
* Copyright (C) 2020-2021 Matt Borgerson
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef XEMU_SHADERS_H
|
|
||||||
#define XEMU_SHADERS_H
|
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
|
||||||
#include <epoxy/gl.h>
|
|
||||||
|
|
||||||
#include "stb_image.h"
|
|
||||||
|
|
||||||
enum SHADER_TYPE {
|
|
||||||
SHADER_TYPE_BLIT,
|
|
||||||
SHADER_TYPE_BLIT_GAMMA,
|
|
||||||
SHADER_TYPE_MASK,
|
|
||||||
SHADER_TYPE_LOGO,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct decal_shader
|
|
||||||
{
|
|
||||||
int flip;
|
|
||||||
float scale;
|
|
||||||
float smoothing;
|
|
||||||
float outline_dist;
|
|
||||||
uint32_t time;
|
|
||||||
|
|
||||||
// GL object handles
|
|
||||||
GLuint prog, vao, vbo, ebo;
|
|
||||||
|
|
||||||
// Uniform locations
|
|
||||||
GLint Mat_loc;
|
|
||||||
GLint FlipY_loc;
|
|
||||||
GLint tex_loc;
|
|
||||||
GLint ScaleOffset_loc;
|
|
||||||
GLint TexScaleOffset_loc;
|
|
||||||
|
|
||||||
GLint ColorPrimary_loc;
|
|
||||||
GLint ColorSecondary_loc;
|
|
||||||
GLint ColorFill_loc;
|
|
||||||
GLint time_loc;
|
|
||||||
GLint scale_loc;
|
|
||||||
GLint palette_loc[256];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct fbo {
|
|
||||||
GLuint fbo;
|
|
||||||
GLuint tex;
|
|
||||||
int w, h;
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extern GLuint main_fb;
|
|
||||||
extern GLint vp[4];
|
|
||||||
|
|
||||||
GLuint compile_shader(GLenum type, const char *src);
|
|
||||||
|
|
||||||
struct decal_shader *create_decal_shader(enum SHADER_TYPE type);
|
|
||||||
void delete_decal_shader(struct decal_shader *s);
|
|
||||||
|
|
||||||
GLuint load_texture_from_file(const char *name);
|
|
||||||
GLuint load_texture_from_memory(const unsigned char *buf, unsigned int size);
|
|
||||||
|
|
||||||
struct fbo *create_fbo(int width, int height);
|
|
||||||
void render_to_default_fb(void);
|
|
||||||
GLuint render_to_fbo(struct fbo *fbo);
|
|
||||||
|
|
||||||
void render_decal(
|
|
||||||
struct decal_shader *s,
|
|
||||||
float x, float y, float w, float h,
|
|
||||||
float tex_x, float tex_y, float tex_w, float tex_h,
|
|
||||||
uint32_t primary, uint32_t secondary, uint32_t fill
|
|
||||||
);
|
|
||||||
|
|
||||||
void render_decal_image(
|
|
||||||
struct decal_shader *s,
|
|
||||||
float x, float y, float w, float h,
|
|
||||||
float tex_x, float tex_y, float tex_w, float tex_h
|
|
||||||
);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,195 +0,0 @@
|
||||||
/*
|
|
||||||
* xemu Automatic Update
|
|
||||||
*
|
|
||||||
* Copyright (C) 2021 Matt Borgerson
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <SDL_filesystem.h>
|
|
||||||
|
|
||||||
#include "util/miniz/miniz.h"
|
|
||||||
|
|
||||||
#include "xemu-update.h"
|
|
||||||
#include "xemu-version.h"
|
|
||||||
|
|
||||||
#if defined(_WIN32)
|
|
||||||
const char *version_host = "raw.githubusercontent.com";
|
|
||||||
const char *version_uri = "/mborgerson/xemu/ppa-snapshot/XEMU_VERSION";
|
|
||||||
const char *download_host = "github.com";
|
|
||||||
const char *download_uri = "/mborgerson/xemu/releases/latest/download/xemu-win-release.zip";
|
|
||||||
#else
|
|
||||||
FIXME
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define CPPHTTPLIB_OPENSSL_SUPPORT 1
|
|
||||||
#include "httplib.h"
|
|
||||||
|
|
||||||
#define DPRINTF(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__);
|
|
||||||
|
|
||||||
Updater::Updater()
|
|
||||||
{
|
|
||||||
m_status = UPDATER_IDLE;
|
|
||||||
m_update_availability = UPDATE_AVAILABILITY_UNKNOWN;
|
|
||||||
m_update_percentage = 0;
|
|
||||||
m_latest_version = "Unknown";
|
|
||||||
m_should_cancel = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Updater::check_for_update(UpdaterCallback on_complete)
|
|
||||||
{
|
|
||||||
if (m_status == UPDATER_IDLE || m_status == UPDATER_ERROR) {
|
|
||||||
m_on_complete = on_complete;
|
|
||||||
qemu_thread_create(&m_thread, "update_worker",
|
|
||||||
&Updater::checker_thread_worker_func,
|
|
||||||
this, QEMU_THREAD_JOINABLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void *Updater::checker_thread_worker_func(void *updater)
|
|
||||||
{
|
|
||||||
((Updater *)updater)->check_for_update_internal();
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Updater::check_for_update_internal()
|
|
||||||
{
|
|
||||||
httplib::SSLClient cli(version_host, 443);
|
|
||||||
cli.set_follow_location(true);
|
|
||||||
cli.set_timeout_sec(5);
|
|
||||||
auto res = cli.Get(version_uri, [this](uint64_t len, uint64_t total) {
|
|
||||||
m_update_percentage = len*100/total;
|
|
||||||
return !m_should_cancel;
|
|
||||||
});
|
|
||||||
if (m_should_cancel) {
|
|
||||||
m_should_cancel = false;
|
|
||||||
m_status = UPDATER_IDLE;
|
|
||||||
goto finished;
|
|
||||||
} else if (!res || res->status != 200) {
|
|
||||||
m_status = UPDATER_ERROR;
|
|
||||||
goto finished;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strcmp(xemu_version, res->body.c_str())) {
|
|
||||||
m_update_availability = UPDATE_AVAILABLE;
|
|
||||||
} else {
|
|
||||||
m_update_availability = UPDATE_NOT_AVAILABLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_latest_version = res->body;
|
|
||||||
m_status = UPDATER_IDLE;
|
|
||||||
finished:
|
|
||||||
if (m_on_complete) {
|
|
||||||
m_on_complete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Updater::update()
|
|
||||||
{
|
|
||||||
if (m_status == UPDATER_IDLE || m_status == UPDATER_ERROR) {
|
|
||||||
m_status = UPDATER_UPDATING;
|
|
||||||
qemu_thread_create(&m_thread, "update_worker",
|
|
||||||
&Updater::update_thread_worker_func,
|
|
||||||
this, QEMU_THREAD_JOINABLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void *Updater::update_thread_worker_func(void *updater)
|
|
||||||
{
|
|
||||||
((Updater *)updater)->update_internal();
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Updater::update_internal()
|
|
||||||
{
|
|
||||||
httplib::SSLClient cli(download_host, 443);
|
|
||||||
cli.set_follow_location(true);
|
|
||||||
cli.set_timeout_sec(5);
|
|
||||||
auto res = cli.Get(download_uri, [this](uint64_t len, uint64_t total) {
|
|
||||||
m_update_percentage = len*100/total;
|
|
||||||
return !m_should_cancel;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (m_should_cancel) {
|
|
||||||
m_should_cancel = false;
|
|
||||||
m_status = UPDATER_IDLE;
|
|
||||||
return;
|
|
||||||
} else if (!res || res->status != 200) {
|
|
||||||
m_status = UPDATER_ERROR;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mz_zip_archive zip;
|
|
||||||
mz_zip_zero_struct(&zip);
|
|
||||||
if (!mz_zip_reader_init_mem(&zip, res->body.data(), res->body.size(), 0)) {
|
|
||||||
DPRINTF("mz_zip_reader_init_mem failed\n");
|
|
||||||
m_status = UPDATER_ERROR;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mz_uint num_files = mz_zip_reader_get_num_files(&zip);
|
|
||||||
for (mz_uint file_idx = 0; file_idx < num_files; file_idx++) {
|
|
||||||
mz_zip_archive_file_stat fstat;
|
|
||||||
if (!mz_zip_reader_file_stat(&zip, file_idx, &fstat)) {
|
|
||||||
DPRINTF("mz_zip_reader_file_stat failed for file #%d\n", file_idx);
|
|
||||||
goto errored;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fstat.m_filename[strlen(fstat.m_filename)-1] == '/') {
|
|
||||||
/* FIXME: mkdirs */
|
|
||||||
DPRINTF("FIXME: subdirs not handled yet\n");
|
|
||||||
goto errored;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *dst_path = g_strdup_printf("%s%s", SDL_GetBasePath(), fstat.m_filename);
|
|
||||||
DPRINTF("extracting %s to %s\n", fstat.m_filename, dst_path);
|
|
||||||
|
|
||||||
if (!strcmp(fstat.m_filename, "xemu.exe")) {
|
|
||||||
// We cannot overwrite current executable, but we can move it
|
|
||||||
char *renamed_path = g_strdup_printf("%s%s", SDL_GetBasePath(), "xemu-previous.exe");
|
|
||||||
MoveFileExA(dst_path, renamed_path, MOVEFILE_REPLACE_EXISTING);
|
|
||||||
g_free(renamed_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mz_zip_reader_extract_to_file(&zip, file_idx, dst_path, 0)) {
|
|
||||||
DPRINTF("mz_zip_reader_extract_to_file failed to create %s\n", dst_path);
|
|
||||||
g_free(dst_path);
|
|
||||||
goto errored;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_free(dst_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_status = UPDATER_UPDATE_SUCCESSFUL;
|
|
||||||
goto cleanup_zip;
|
|
||||||
errored:
|
|
||||||
m_status = UPDATER_ERROR;
|
|
||||||
cleanup_zip:
|
|
||||||
mz_zip_reader_end(&zip);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
extern char **gArgv;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Updater::restart_to_updated()
|
|
||||||
{
|
|
||||||
char *target_exec = g_strdup_printf("%s%s", SDL_GetBasePath(), "xemu.exe");
|
|
||||||
DPRINTF("Restarting to updated executable %s\n", target_exec);
|
|
||||||
_execv(target_exec, gArgv);
|
|
||||||
DPRINTF("Launching updated executable failed\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
/*
|
|
||||||
* xemu Automatic Update
|
|
||||||
*
|
|
||||||
* Copyright (C) 2021 Matt Borgerson
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef XEMU_UPDATE_H
|
|
||||||
#define XEMU_UPDATE_H
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qemu-common.h"
|
|
||||||
#include "qemu/thread.h"
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
UPDATE_AVAILABILITY_UNKNOWN,
|
|
||||||
UPDATE_NOT_AVAILABLE,
|
|
||||||
UPDATE_AVAILABLE
|
|
||||||
} UpdateAvailability;
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
UPDATER_IDLE,
|
|
||||||
UPDATER_ERROR,
|
|
||||||
UPDATER_CHECKING_FOR_UPDATE,
|
|
||||||
UPDATER_UPDATING,
|
|
||||||
UPDATER_UPDATE_SUCCESSFUL
|
|
||||||
} UpdateStatus;
|
|
||||||
|
|
||||||
using UpdaterCallback = std::function<void(void)>;
|
|
||||||
|
|
||||||
class Updater {
|
|
||||||
private:
|
|
||||||
UpdateAvailability m_update_availability;
|
|
||||||
int m_update_percentage;
|
|
||||||
QemuThread m_thread;
|
|
||||||
std::string m_latest_version;
|
|
||||||
bool m_should_cancel;
|
|
||||||
UpdateStatus m_status;
|
|
||||||
UpdaterCallback m_on_complete;
|
|
||||||
|
|
||||||
public:
|
|
||||||
Updater();
|
|
||||||
UpdateStatus get_status() { return m_status; }
|
|
||||||
UpdateAvailability get_update_availability() { return m_update_availability; }
|
|
||||||
bool is_errored() { return m_status == UPDATER_ERROR; }
|
|
||||||
bool is_pending_restart() { return m_status == UPDATER_UPDATE_SUCCESSFUL; }
|
|
||||||
bool is_update_available() { return m_update_availability == UPDATE_AVAILABLE; }
|
|
||||||
bool is_checking_for_update() { return m_status == UPDATER_CHECKING_FOR_UPDATE; }
|
|
||||||
bool is_updating() { return m_status == UPDATER_UPDATING; }
|
|
||||||
std::string get_update_version() { return m_latest_version; }
|
|
||||||
void cancel() { m_should_cancel = true; }
|
|
||||||
void update();
|
|
||||||
void update_internal();
|
|
||||||
void check_for_update(UpdaterCallback on_complete = nullptr);
|
|
||||||
void check_for_update_internal();
|
|
||||||
int get_update_progress_percentage() { return m_update_percentage; }
|
|
||||||
static void *update_thread_worker_func(void *updater);
|
|
||||||
static void *checker_thread_worker_func(void *updater);
|
|
||||||
void restart_to_updated(void);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
132
ui/xemu.c
132
ui/xemu.c
|
@ -43,10 +43,10 @@
|
||||||
#include "sysemu/runstate.h"
|
#include "sysemu/runstate.h"
|
||||||
#include "sysemu/runstate-action.h"
|
#include "sysemu/runstate-action.h"
|
||||||
#include "sysemu/sysemu.h"
|
#include "sysemu/sysemu.h"
|
||||||
#include "xemu-hud.h"
|
#include "xui/xemu-hud.h"
|
||||||
#include "xemu-input.h"
|
#include "xemu-input.h"
|
||||||
#include "xemu-settings.h"
|
#include "xemu-settings.h"
|
||||||
#include "xemu-shaders.h"
|
// #include "xemu-shaders.h"
|
||||||
#include "xemu-version.h"
|
#include "xemu-version.h"
|
||||||
#include "xemu-os-utils.h"
|
#include "xemu-os-utils.h"
|
||||||
|
|
||||||
|
@ -55,6 +55,8 @@
|
||||||
#include "hw/xbox/smbus.h" // For eject, drive tray
|
#include "hw/xbox/smbus.h" // For eject, drive tray
|
||||||
#include "hw/xbox/nv2a/nv2a.h"
|
#include "hw/xbox/nv2a/nv2a.h"
|
||||||
|
|
||||||
|
#include <stb_image.h>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// Provide hint to prefer high-performance graphics for hybrid systems
|
// Provide hint to prefer high-performance graphics for hybrid systems
|
||||||
// https://gpuopen.com/learn/amdpowerxpressrequesthighperformance/
|
// https://gpuopen.com/learn/amdpowerxpressrequesthighperformance/
|
||||||
|
@ -108,7 +110,7 @@ static SDL_Cursor *guest_sprite;
|
||||||
static Notifier mouse_mode_notifier;
|
static Notifier mouse_mode_notifier;
|
||||||
static SDL_Window *m_window;
|
static SDL_Window *m_window;
|
||||||
static SDL_GLContext m_context;
|
static SDL_GLContext m_context;
|
||||||
struct decal_shader *blit;
|
// struct decal_shader *blit;
|
||||||
|
|
||||||
static QemuSemaphore display_init_sem;
|
static QemuSemaphore display_init_sem;
|
||||||
|
|
||||||
|
@ -845,28 +847,58 @@ static void sdl2_display_very_early_init(DisplayOptions *o)
|
||||||
#endif
|
#endif
|
||||||
, xemu_version);
|
, xemu_version);
|
||||||
|
|
||||||
|
// Decide window size
|
||||||
|
int min_window_width = 640;
|
||||||
|
int min_window_height = 480;
|
||||||
|
int window_width = min_window_width;
|
||||||
|
int window_height = min_window_height;
|
||||||
|
|
||||||
|
const int res_table[][2] = {
|
||||||
|
{640, 480},
|
||||||
|
{1280, 720},
|
||||||
|
{1280, 800},
|
||||||
|
{1280, 960},
|
||||||
|
{1920, 1080},
|
||||||
|
{2560, 1440},
|
||||||
|
{2560, 1600},
|
||||||
|
{2560, 1920},
|
||||||
|
{3840, 2160}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (g_config.display.window.startup_size == CONFIG_DISPLAY_WINDOW_STARTUP_SIZE_LAST_USED) {
|
||||||
|
window_width = g_config.display.window.last_width;
|
||||||
|
window_height = g_config.display.window.last_height;
|
||||||
|
} else {
|
||||||
|
window_width = res_table[g_config.display.window.startup_size-1][0];
|
||||||
|
window_height = res_table[g_config.display.window.startup_size-1][1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window_width < min_window_width) {
|
||||||
|
window_width = min_window_width;
|
||||||
|
}
|
||||||
|
if (window_height < min_window_height) {
|
||||||
|
window_height = min_window_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||||
|
|
||||||
// Create main window
|
// Create main window
|
||||||
m_window = SDL_CreateWindow(
|
m_window = SDL_CreateWindow(
|
||||||
title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480,
|
title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, window_width, window_height,
|
||||||
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
window_flags);
|
||||||
if (m_window == NULL) {
|
if (m_window == NULL) {
|
||||||
fprintf(stderr, "Failed to create main window\n");
|
fprintf(stderr, "Failed to create main window\n");
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
g_free(title);
|
g_free(title);
|
||||||
|
SDL_SetWindowMinimumSize(m_window, min_window_width, min_window_height);
|
||||||
|
|
||||||
SDL_DisplayMode disp_mode;
|
SDL_DisplayMode disp_mode;
|
||||||
SDL_GetCurrentDisplayMode(SDL_GetWindowDisplayIndex(m_window), &disp_mode);
|
SDL_GetCurrentDisplayMode(SDL_GetWindowDisplayIndex(m_window), &disp_mode);
|
||||||
|
if (disp_mode.w < window_width || disp_mode.h < window_height) {
|
||||||
int win_w = g_config.display.window.last_width,
|
SDL_SetWindowSize(m_window, min_window_width, min_window_height);
|
||||||
win_h = g_config.display.window.last_height;
|
SDL_SetWindowPosition(m_window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||||
|
|
||||||
if (win_w > 0 && win_h > 0) {
|
|
||||||
if (disp_mode.w >= win_w && disp_mode.h >= win_h) {
|
|
||||||
SDL_SetWindowSize(m_window, win_w, win_h);
|
|
||||||
SDL_SetWindowPosition(m_window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_context = SDL_GL_CreateContext(m_window);
|
m_context = SDL_GL_CreateContext(m_window);
|
||||||
|
@ -923,9 +955,9 @@ static void sdl2_display_early_init(DisplayOptions *o)
|
||||||
display_opengl = 1;
|
display_opengl = 1;
|
||||||
|
|
||||||
SDL_GL_MakeCurrent(m_window, m_context);
|
SDL_GL_MakeCurrent(m_window, m_context);
|
||||||
SDL_GL_SetSwapInterval(0);
|
SDL_GL_SetSwapInterval(g_config.display.window.vsync ? 1 : 0);
|
||||||
xemu_hud_init(m_window, m_context);
|
xemu_hud_init(m_window, m_context);
|
||||||
blit = create_decal_shader(SHADER_TYPE_BLIT_GAMMA);
|
// blit = create_decal_shader(SHADER_TYPE_BLIT_GAMMA);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
|
static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
|
||||||
|
@ -942,6 +974,8 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
|
||||||
|
|
||||||
gui_fullscreen = o->has_full_screen && o->full_screen;
|
gui_fullscreen = o->has_full_screen && o->full_screen;
|
||||||
|
|
||||||
|
gui_fullscreen |= g_config.display.window.fullscreen_on_startup;
|
||||||
|
|
||||||
#if 1
|
#if 1
|
||||||
// Explicitly set number of outputs to 1 for a single screen. We don't need
|
// Explicitly set number of outputs to 1 for a single screen. We don't need
|
||||||
// multiple for now, but maybe in the future debug stuff can go on a second
|
// multiple for now, but maybe in the future debug stuff can go on a second
|
||||||
|
@ -1145,6 +1179,7 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl)
|
||||||
*/
|
*/
|
||||||
GLuint tex = nv2a_get_framebuffer_surface();
|
GLuint tex = nv2a_get_framebuffer_surface();
|
||||||
if (tex == 0) {
|
if (tex == 0) {
|
||||||
|
// FIXME: Don't upload if notdirty
|
||||||
xb_surface_gl_create_texture(scon->surface);
|
xb_surface_gl_create_texture(scon->surface);
|
||||||
scon->updates++;
|
scon->updates++;
|
||||||
tex = scon->surface->texture;
|
tex = scon->surface->texture;
|
||||||
|
@ -1160,72 +1195,9 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl)
|
||||||
qemu_mutex_lock_iothread();
|
qemu_mutex_lock_iothread();
|
||||||
sdl2_poll_events(scon);
|
sdl2_poll_events(scon);
|
||||||
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, tex);
|
|
||||||
|
|
||||||
// Get texture dimensions
|
|
||||||
int tw, th;
|
|
||||||
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &tw);
|
|
||||||
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &th);
|
|
||||||
|
|
||||||
// Get window dimensions
|
|
||||||
int ww, wh;
|
|
||||||
SDL_GL_GetDrawableSize(scon->real_window, &ww, &wh);
|
|
||||||
|
|
||||||
// Calculate scaling factors
|
|
||||||
float scale[2];
|
|
||||||
if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_STRETCH) {
|
|
||||||
// Stretch to fit
|
|
||||||
scale[0] = 1.0;
|
|
||||||
scale[1] = 1.0;
|
|
||||||
} else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_CENTER) {
|
|
||||||
// Centered
|
|
||||||
scale[0] = (float)tw/(float)ww;
|
|
||||||
scale[1] = (float)th/(float)wh;
|
|
||||||
} else {
|
|
||||||
float t_ratio;
|
|
||||||
if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_16_9) {
|
|
||||||
// Scale to fit window using a fixed 16:9 aspect ratio
|
|
||||||
t_ratio = 16.0f/9.0f;
|
|
||||||
} else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_4_3) {
|
|
||||||
t_ratio = 4.0f/3.0f;
|
|
||||||
} else {
|
|
||||||
// Scale to fit, preserving framebuffer aspect ratio
|
|
||||||
t_ratio = (float)tw/(float)th;
|
|
||||||
}
|
|
||||||
|
|
||||||
float w_ratio = (float)ww/(float)wh;
|
|
||||||
if (w_ratio >= t_ratio) {
|
|
||||||
scale[0] = t_ratio/w_ratio;
|
|
||||||
scale[1] = 1.0;
|
|
||||||
} else {
|
|
||||||
scale[0] = 1.0;
|
|
||||||
scale[1] = w_ratio/t_ratio;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render framebuffer and GUI
|
|
||||||
struct decal_shader *s = blit;
|
|
||||||
s->flip = flip_required;
|
|
||||||
glViewport(0, 0, ww, wh);
|
|
||||||
glUseProgram(s->prog);
|
|
||||||
glBindVertexArray(s->vao);
|
|
||||||
glUniform1i(s->FlipY_loc, s->flip);
|
|
||||||
glUniform4f(s->ScaleOffset_loc, scale[0], scale[1], 0, 0);
|
|
||||||
glUniform4f(s->TexScaleOffset_loc, 1.0, 1.0, 0, 0);
|
|
||||||
glUniform1i(s->tex_loc, 0);
|
|
||||||
|
|
||||||
const uint8_t *palette = nv2a_get_dac_palette();
|
|
||||||
for (int i = 0; i < 256; i++) {
|
|
||||||
uint32_t e = (palette[i * 3 + 2] << 16) | (palette[i * 3 + 1] << 8) |
|
|
||||||
palette[i * 3];
|
|
||||||
glUniform1ui(s->palette_loc[i], e);
|
|
||||||
}
|
|
||||||
|
|
||||||
glClearColor(0, 0, 0, 0);
|
glClearColor(0, 0, 0, 0);
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL);
|
xemu_hud_set_framebuffer_texture(tex, flip_required);
|
||||||
|
|
||||||
xemu_hud_render();
|
xemu_hud_render();
|
||||||
|
|
||||||
// Release BQL before swapping (which may sleep if swap interval is not immediate)
|
// Release BQL before swapping (which may sleep if swap interval is not immediate)
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#include "common.hh"
|
||||||
|
#include "misc.hh"
|
||||||
|
#include "xemu-hud.h"
|
||||||
|
|
||||||
|
void ActionEjectDisc(void)
|
||||||
|
{
|
||||||
|
xemu_settings_set_string(&g_config.sys.files.dvd_path, "");
|
||||||
|
xemu_eject_disc();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActionLoadDisc(void)
|
||||||
|
{
|
||||||
|
const char *iso_file_filters = ".iso Files\0*.iso\0All Files\0*.*\0";
|
||||||
|
const char *new_disc_path =
|
||||||
|
PausedFileOpen(NOC_FILE_DIALOG_OPEN, iso_file_filters,
|
||||||
|
g_config.sys.files.dvd_path, NULL);
|
||||||
|
if (new_disc_path == NULL) {
|
||||||
|
/* Cancelled */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
xemu_settings_set_string(&g_config.sys.files.dvd_path, new_disc_path);
|
||||||
|
xemu_load_disc(new_disc_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActionTogglePause(void)
|
||||||
|
{
|
||||||
|
if (runstate_is_running()) {
|
||||||
|
vm_stop(RUN_STATE_PAUSED);
|
||||||
|
} else {
|
||||||
|
vm_start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActionReset(void)
|
||||||
|
{
|
||||||
|
qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActionShutdown(void)
|
||||||
|
{
|
||||||
|
qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActionScreenshot(void)
|
||||||
|
{
|
||||||
|
g_screenshot_pending = true;
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
void ActionEjectDisc();
|
||||||
|
void ActionLoadDisc();
|
||||||
|
void ActionTogglePause();
|
||||||
|
void ActionReset();
|
||||||
|
void ActionShutdown();
|
||||||
|
void ActionScreenshot();
|
|
@ -0,0 +1,161 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#include <cmath>
|
||||||
|
#include "common.hh"
|
||||||
|
#include "animation.hh"
|
||||||
|
|
||||||
|
Animation::Animation(float duration)
|
||||||
|
: m_duration(duration)
|
||||||
|
{
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::Reset()
|
||||||
|
{
|
||||||
|
m_acc = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::SetDuration(float duration)
|
||||||
|
{
|
||||||
|
m_duration = duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::Step()
|
||||||
|
{
|
||||||
|
if (g_config.display.ui.use_animations) {
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
m_acc += io.DeltaTime;
|
||||||
|
} else {
|
||||||
|
m_acc = m_duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Animation::IsComplete()
|
||||||
|
{
|
||||||
|
return m_acc >= m_duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
float Animation::GetLinearValue()
|
||||||
|
{
|
||||||
|
if (m_acc < m_duration) {
|
||||||
|
return m_acc / m_duration;
|
||||||
|
} else {
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::SetLinearValue(float t)
|
||||||
|
{
|
||||||
|
m_acc = t * m_duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
float Animation::GetSinInterpolatedValue()
|
||||||
|
{
|
||||||
|
return sin(GetLinearValue() * M_PI * 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
EasingAnimation::EasingAnimation(float ease_in_duration, float ease_out_duration)
|
||||||
|
: m_state(AnimationState::PreEasingIn),
|
||||||
|
m_duration_out(ease_out_duration),
|
||||||
|
m_duration_in(ease_in_duration) {}
|
||||||
|
|
||||||
|
void EasingAnimation::EaseIn()
|
||||||
|
{
|
||||||
|
EaseIn(m_duration_in);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EasingAnimation::EaseIn(float duration)
|
||||||
|
{
|
||||||
|
if (duration == 0) {
|
||||||
|
m_state = AnimationState::Idle;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
float t = m_animation.GetLinearValue();
|
||||||
|
m_animation.SetDuration(duration);
|
||||||
|
if (m_state == AnimationState::EasingOut) {
|
||||||
|
m_animation.SetLinearValue(1-t);
|
||||||
|
} else if (m_state != AnimationState::EasingIn) {
|
||||||
|
m_animation.Reset();
|
||||||
|
}
|
||||||
|
m_state = AnimationState::EasingIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EasingAnimation::EaseOut()
|
||||||
|
{
|
||||||
|
EaseOut(m_duration_out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EasingAnimation::EaseOut(float duration)
|
||||||
|
{
|
||||||
|
if (duration == 0) {
|
||||||
|
m_state = AnimationState::PostEasingOut;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
float t = m_animation.GetLinearValue();
|
||||||
|
m_animation.SetDuration(duration);
|
||||||
|
if (m_state == AnimationState::EasingIn) {
|
||||||
|
m_animation.SetLinearValue(1-t);
|
||||||
|
} else if (m_state != AnimationState::EasingOut) {
|
||||||
|
m_animation.Reset();
|
||||||
|
}
|
||||||
|
m_state = AnimationState::EasingOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EasingAnimation::Step()
|
||||||
|
{
|
||||||
|
if (m_state == AnimationState::EasingIn ||
|
||||||
|
m_state == AnimationState::EasingOut) {
|
||||||
|
m_animation.Step();
|
||||||
|
if (m_animation.IsComplete()) {
|
||||||
|
if (m_state == AnimationState::EasingIn) {
|
||||||
|
m_state = AnimationState::Idle;
|
||||||
|
} else if (m_state == AnimationState::EasingOut) {
|
||||||
|
m_state = AnimationState::PostEasingOut;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float EasingAnimation::GetLinearValue()
|
||||||
|
{
|
||||||
|
switch (m_state) {
|
||||||
|
case AnimationState::PreEasingIn: return 0;
|
||||||
|
case AnimationState::EasingIn: return m_animation.GetLinearValue();
|
||||||
|
case AnimationState::Idle: return 1;
|
||||||
|
case AnimationState::EasingOut: return 1 - m_animation.GetLinearValue();
|
||||||
|
case AnimationState::PostEasingOut: return 0;
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float EasingAnimation::GetSinInterpolatedValue()
|
||||||
|
{
|
||||||
|
return sin(GetLinearValue() * M_PI * 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EasingAnimation::IsAnimating()
|
||||||
|
{
|
||||||
|
return m_state == AnimationState::EasingIn ||
|
||||||
|
m_state == AnimationState::EasingOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EasingAnimation::IsComplete()
|
||||||
|
{
|
||||||
|
return m_state == AnimationState::PostEasingOut;
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
#include "common.hh"
|
||||||
|
|
||||||
|
const ImVec2 EASE_VECTOR_DOWN = ImVec2(0, -25);
|
||||||
|
const ImVec2 EASE_VECTOR_LEFT = ImVec2(25, 0);
|
||||||
|
const ImVec2 EASE_VECTOR_RIGHT = ImVec2(-25, 0);
|
||||||
|
|
||||||
|
enum AnimationState
|
||||||
|
{
|
||||||
|
PreEasingIn,
|
||||||
|
EasingIn,
|
||||||
|
Idle,
|
||||||
|
EasingOut,
|
||||||
|
PostEasingOut
|
||||||
|
};
|
||||||
|
|
||||||
|
// Step a value from 0 to 1 over some duration of time.
|
||||||
|
class Animation
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
float m_duration;
|
||||||
|
float m_acc;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Animation(float duration = 0);
|
||||||
|
void Reset();
|
||||||
|
void SetDuration(float duration);
|
||||||
|
void Step();
|
||||||
|
bool IsComplete();
|
||||||
|
float GetLinearValue();
|
||||||
|
void SetLinearValue(float t);
|
||||||
|
float GetSinInterpolatedValue();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stateful animation sequence for easing in and out: 0->1->0
|
||||||
|
class EasingAnimation
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
AnimationState m_state;
|
||||||
|
Animation m_animation;
|
||||||
|
float m_duration_out;
|
||||||
|
float m_duration_in;
|
||||||
|
|
||||||
|
public:
|
||||||
|
EasingAnimation(float ease_in_duration = 1.0, float ease_out_duration = 1.0);
|
||||||
|
void EaseIn();
|
||||||
|
void EaseIn(float duration);
|
||||||
|
void EaseOut();
|
||||||
|
void EaseOut(float duration);
|
||||||
|
void Step();
|
||||||
|
float GetLinearValue();
|
||||||
|
float GetSinInterpolatedValue();
|
||||||
|
bool IsAnimating();
|
||||||
|
bool IsComplete();
|
||||||
|
};
|
|
@ -0,0 +1,55 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <epoxy/gl.h>
|
||||||
|
#include "ui/xemu-settings.h"
|
||||||
|
|
||||||
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||||
|
#include <imgui.h>
|
||||||
|
#include <imgui_internal.h>
|
||||||
|
#include <imgui_impl_sdl.h>
|
||||||
|
#include <imgui_impl_opengl3.h>
|
||||||
|
#include <implot.h>
|
||||||
|
#include <stb_image.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <noc_file_dialog.h>
|
||||||
|
|
||||||
|
// Include necessary QEMU headers
|
||||||
|
#include "qemu/osdep.h"
|
||||||
|
#include "qemu-common.h"
|
||||||
|
#include "qapi/error.h"
|
||||||
|
#include "sysemu/sysemu.h"
|
||||||
|
#include "sysemu/runstate.h"
|
||||||
|
#include "hw/xbox/mcpx/apu_debug.h"
|
||||||
|
#include "hw/xbox/nv2a/debug.h"
|
||||||
|
#include "hw/xbox/nv2a/nv2a.h"
|
||||||
|
|
||||||
|
#undef typename
|
||||||
|
#undef atomic_fetch_add
|
||||||
|
#undef atomic_fetch_and
|
||||||
|
#undef atomic_fetch_xor
|
||||||
|
#undef atomic_fetch_or
|
||||||
|
#undef atomic_fetch_sub
|
||||||
|
}
|
||||||
|
|
||||||
|
extern bool g_screenshot_pending;
|
||||||
|
extern float g_main_menu_height;
|
|
@ -0,0 +1,220 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#include <string>
|
||||||
|
#include "common.hh"
|
||||||
|
#include "compat.hh"
|
||||||
|
#include "widgets.hh"
|
||||||
|
#include "viewport-manager.hh"
|
||||||
|
#include "font-manager.hh"
|
||||||
|
#include "xemu-version.h"
|
||||||
|
#include "reporting.hh"
|
||||||
|
#include "../xemu-settings.h"
|
||||||
|
#include "../xemu-os-utils.h"
|
||||||
|
|
||||||
|
CompatibilityReporter::CompatibilityReporter()
|
||||||
|
{
|
||||||
|
is_open = false;
|
||||||
|
|
||||||
|
report.token = "";
|
||||||
|
report.xemu_version = xemu_version;
|
||||||
|
report.xemu_branch = xemu_branch;
|
||||||
|
report.xemu_commit = xemu_commit;
|
||||||
|
report.xemu_date = xemu_date;
|
||||||
|
report.os_platform = xemu_get_os_platform();
|
||||||
|
report.os_version = xemu_get_os_info();
|
||||||
|
report.cpu = xemu_get_cpu_info();
|
||||||
|
dirty = true;
|
||||||
|
is_xbe_identified = false;
|
||||||
|
did_send = send_result = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CompatibilityReporter::~CompatibilityReporter()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompatibilityReporter::Draw()
|
||||||
|
{
|
||||||
|
if (!is_open) return;
|
||||||
|
|
||||||
|
const char *playability_names[] = {
|
||||||
|
"Broken",
|
||||||
|
"Intro",
|
||||||
|
"Starts",
|
||||||
|
"Playable",
|
||||||
|
"Perfect",
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *playability_descriptions[] = {
|
||||||
|
"This title crashes very soon after launching, or displays nothing at all.",
|
||||||
|
"This title displays an intro sequence, but fails to make it to gameplay.",
|
||||||
|
"This title starts, but may crash or have significant issues.",
|
||||||
|
"This title is playable, but may have minor issues.",
|
||||||
|
"This title is playable from start to finish with no noticable issues."
|
||||||
|
};
|
||||||
|
|
||||||
|
ImGui::SetNextWindowContentSize(ImVec2(550.0f*g_viewport_mgr.m_scale, 0.0f));
|
||||||
|
if (!ImGui::Begin("Report Compatibility", &is_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::IsWindowAppearing()) {
|
||||||
|
report.gl_vendor = (const char *)glGetString(GL_VENDOR);
|
||||||
|
report.gl_renderer = (const char *)glGetString(GL_RENDERER);
|
||||||
|
report.gl_version = (const char *)glGetString(GL_VERSION);
|
||||||
|
report.gl_shading_language_version = (const char *)glGetString(GL_SHADING_LANGUAGE_VERSION);
|
||||||
|
struct xbe *xbe = xemu_get_xbe_info();
|
||||||
|
is_xbe_identified = xbe != NULL;
|
||||||
|
if (is_xbe_identified) {
|
||||||
|
report.SetXbeData(xbe);
|
||||||
|
}
|
||||||
|
did_send = send_result = false;
|
||||||
|
|
||||||
|
playability = 3; // Playable
|
||||||
|
report.compat_rating = playability_names[playability];
|
||||||
|
description[0] = '\x00';
|
||||||
|
report.compat_comments = description;
|
||||||
|
|
||||||
|
strncpy(token_buf, g_config.general.user_token, sizeof(token_buf)-1);
|
||||||
|
report.token = token_buf;
|
||||||
|
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_xbe_identified) {
|
||||||
|
ImGui::TextWrapped(
|
||||||
|
"An XBE could not be identified. Please launch an official "
|
||||||
|
"Xbox title to submit a compatibility report.");
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TextWrapped(
|
||||||
|
"If you would like to help improve xemu by submitting a compatibility report for this "
|
||||||
|
"title, please select an appropriate playability level, enter a "
|
||||||
|
"brief description, then click 'Send'."
|
||||||
|
"\n\n"
|
||||||
|
"Note: By submitting a report, you acknowledge and consent to "
|
||||||
|
"collection, archival, and publication of information as outlined "
|
||||||
|
"in 'Privacy Disclosure' below.");
|
||||||
|
|
||||||
|
ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
|
||||||
|
|
||||||
|
ImGui::Columns(2, "", false);
|
||||||
|
ImGui::SetColumnWidth(0, ImGui::GetWindowWidth()*0.25);
|
||||||
|
|
||||||
|
ImGui::Text("User Token");
|
||||||
|
ImGui::SameLine();
|
||||||
|
HelpMarker("This is a unique access token used to authorize submission of the report. To request a token, click 'Get Token'.");
|
||||||
|
ImGui::NextColumn();
|
||||||
|
float item_width = ImGui::GetColumnWidth()*0.75-20*g_viewport_mgr.m_scale;
|
||||||
|
ImGui::SetNextItemWidth(item_width);
|
||||||
|
ImGui::PushFont(g_font_mgr.m_fixed_width_font);
|
||||||
|
if (ImGui::InputText("###UserToken", token_buf, sizeof(token_buf), 0)) {
|
||||||
|
xemu_settings_set_string(&g_config.general.user_token, token_buf);
|
||||||
|
report.token = token_buf;
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
ImGui::PopFont();
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Get Token")) {
|
||||||
|
xemu_open_web_browser("https://reports.xemu.app");
|
||||||
|
}
|
||||||
|
ImGui::NextColumn();
|
||||||
|
|
||||||
|
ImGui::Text("Playability");
|
||||||
|
ImGui::NextColumn();
|
||||||
|
ImGui::SetNextItemWidth(item_width);
|
||||||
|
if (ImGui::Combo("###PlayabilityRating", &playability,
|
||||||
|
"Broken\0" "Intro/Menus\0" "Starts\0" "Playable\0" "Perfect\0")) {
|
||||||
|
report.compat_rating = playability_names[playability];
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
HelpMarker(playability_descriptions[playability]);
|
||||||
|
ImGui::NextColumn();
|
||||||
|
|
||||||
|
ImGui::Columns(1);
|
||||||
|
|
||||||
|
ImGui::Text("Description");
|
||||||
|
if (ImGui::InputTextMultiline("###desc", description, sizeof(description), ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 6), 0)) {
|
||||||
|
report.compat_comments = description;
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::TreeNode("Report Details")) {
|
||||||
|
ImGui::PushFont(g_font_mgr.m_fixed_width_font);
|
||||||
|
if (dirty) {
|
||||||
|
serialized_report = report.GetSerializedReport();
|
||||||
|
dirty = false;
|
||||||
|
}
|
||||||
|
ImGui::InputTextMultiline("##build_info", (char*)serialized_report.c_str(), strlen(serialized_report.c_str())+1, ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 7), ImGuiInputTextFlags_ReadOnly);
|
||||||
|
ImGui::PopFont();
|
||||||
|
ImGui::TreePop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::TreeNode("Privacy Disclosure (Please read before submission!)")) {
|
||||||
|
ImGui::TextWrapped(
|
||||||
|
"By volunteering to submit a compatibility report, basic information about your "
|
||||||
|
"computer is collected, including: your operating system version, CPU model, "
|
||||||
|
"graphics card/driver information, and details about the title which are "
|
||||||
|
"extracted from the executable in memory. The contents of this report can be "
|
||||||
|
"seen before submission by expanding 'Report Details'."
|
||||||
|
"\n\n"
|
||||||
|
"Like many websites, upon submission, the public IP address of your computer is "
|
||||||
|
"also recorded with your report. If provided, the identity associated with your "
|
||||||
|
"token is also recorded."
|
||||||
|
"\n\n"
|
||||||
|
"This information will be archived and used to analyze, resolve problems with, "
|
||||||
|
"and improve the application. This information may be made publicly visible, "
|
||||||
|
"for example: to anyone who wishes to see the playability status of a title, as "
|
||||||
|
"indicated by your report.");
|
||||||
|
ImGui::TreePop();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
|
||||||
|
|
||||||
|
if (did_send) {
|
||||||
|
if (send_result) {
|
||||||
|
ImGui::Text("Sent! Thanks.");
|
||||||
|
} else {
|
||||||
|
ImGui::Text("Error: %s (%d)", report.GetResultMessage().c_str(), report.GetResultCode());
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SetCursorPosX(ImGui::GetWindowWidth()-(120+10)*g_viewport_mgr.m_scale);
|
||||||
|
|
||||||
|
ImGui::SetItemDefaultFocus();
|
||||||
|
if (ImGui::Button("Send", ImVec2(120*g_viewport_mgr.m_scale, 0))) {
|
||||||
|
did_send = true;
|
||||||
|
send_result = report.Send();
|
||||||
|
if (send_result) {
|
||||||
|
is_open = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
CompatibilityReporter compatibility_reporter_window;
|
|
@ -0,0 +1,41 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include "reporting.hh"
|
||||||
|
|
||||||
|
class CompatibilityReporter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CompatibilityReport report;
|
||||||
|
bool dirty;
|
||||||
|
bool is_open;
|
||||||
|
bool is_xbe_identified;
|
||||||
|
bool did_send, send_result;
|
||||||
|
char token_buf[512];
|
||||||
|
int playability;
|
||||||
|
char description[1024];
|
||||||
|
std::string serialized_report;
|
||||||
|
|
||||||
|
CompatibilityReporter();
|
||||||
|
~CompatibilityReporter();
|
||||||
|
void Draw();
|
||||||
|
};
|
||||||
|
|
||||||
|
extern CompatibilityReporter compatibility_reporter_window;
|
|
@ -0,0 +1,323 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#include "debug.hh"
|
||||||
|
#include "common.hh"
|
||||||
|
#include "misc.hh"
|
||||||
|
#include "font-manager.hh"
|
||||||
|
#include "viewport-manager.hh"
|
||||||
|
|
||||||
|
DebugApuWindow::DebugApuWindow() : m_is_open(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugApuWindow::Draw()
|
||||||
|
{
|
||||||
|
if (!m_is_open)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ImGui::SetNextWindowContentSize(ImVec2(600.0f*g_viewport_mgr.m_scale, 0.0f));
|
||||||
|
if (!ImGui::Begin("Audio Debug", &m_is_open,
|
||||||
|
ImGuiWindowFlags_NoCollapse |
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct McpxApuDebug *dbg = mcpx_apu_get_debug_info();
|
||||||
|
|
||||||
|
|
||||||
|
ImGui::Columns(2, "", false);
|
||||||
|
int now = SDL_GetTicks() % 1000;
|
||||||
|
float t = now/1000.0f;
|
||||||
|
float freq = 1;
|
||||||
|
float v = fabs(sin(M_PI*t*freq));
|
||||||
|
float c_active = mix(0.4, 0.97, v);
|
||||||
|
float c_inactive = 0.2f;
|
||||||
|
|
||||||
|
int voice_monitor = -1;
|
||||||
|
int voice_info = -1;
|
||||||
|
int voice_mute = -1;
|
||||||
|
|
||||||
|
ImGui::PushFont(g_font_mgr.m_fixed_width_font);
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0);
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 4));
|
||||||
|
for (int i = 0; i < 256; i++)
|
||||||
|
{
|
||||||
|
if (i % 16) {
|
||||||
|
ImGui::SameLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
float c, s, h;
|
||||||
|
h = 0.6;
|
||||||
|
if (dbg->vp.v[i].active) {
|
||||||
|
if (dbg->vp.v[i].paused) {
|
||||||
|
c = c_inactive;
|
||||||
|
s = 0.4;
|
||||||
|
} else {
|
||||||
|
c = c_active;
|
||||||
|
s = 0.7;
|
||||||
|
}
|
||||||
|
if (mcpx_apu_debug_is_muted(i)) {
|
||||||
|
h = 1.0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c = c_inactive;
|
||||||
|
s = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::PushID(i);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(h, s, c));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(h, s, 0.8));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(h, 0.8f, 1.0));
|
||||||
|
char buf[12];
|
||||||
|
snprintf(buf, sizeof(buf), "%02x", i);
|
||||||
|
ImGui::Button(buf);
|
||||||
|
if (/*dbg->vp.v[i].active &&*/ ImGui::IsItemHovered()) {
|
||||||
|
voice_monitor = i;
|
||||||
|
voice_info = i;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemClicked(1)) {
|
||||||
|
voice_mute = i;
|
||||||
|
}
|
||||||
|
ImGui::PopStyleColor(3);
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
ImGui::PopStyleVar(3);
|
||||||
|
ImGui::PopFont();
|
||||||
|
|
||||||
|
if (voice_info >= 0) {
|
||||||
|
const struct McpxApuDebugVoice *voice = &dbg->vp.v[voice_info];
|
||||||
|
ImGui::BeginTooltip();
|
||||||
|
bool is_paused = voice->paused;
|
||||||
|
ImGui::Text("Voice 0x%x/%d %s", voice_info, voice_info, is_paused ? "(Paused)" : "");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Text(voice->stereo ? "Stereo" : "Mono");
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::PushFont(g_font_mgr.m_fixed_width_font);
|
||||||
|
|
||||||
|
const char *noyes[2] = { "NO", "YES" };
|
||||||
|
ImGui::Text("Stream: %-3s Loop: %-3s Persist: %-3s Multipass: %-3s "
|
||||||
|
"Linked: %-3s",
|
||||||
|
noyes[voice->stream], noyes[voice->loop],
|
||||||
|
noyes[voice->persist], noyes[voice->multipass],
|
||||||
|
noyes[voice->linked]);
|
||||||
|
|
||||||
|
const char *cs[4] = { "1 byte", "2 bytes", "ADPCM", "4 bytes" };
|
||||||
|
const char *ss[4] = {
|
||||||
|
"Unsigned 8b PCM",
|
||||||
|
"Signed 16b PCM",
|
||||||
|
"Signed 24b PCM",
|
||||||
|
"Signed 32b PCM"
|
||||||
|
};
|
||||||
|
|
||||||
|
assert(voice->container_size < 4);
|
||||||
|
assert(voice->sample_size < 4);
|
||||||
|
ImGui::Text("Container Size: %s, Sample Size: %s, Samples per Block: %d",
|
||||||
|
cs[voice->container_size], ss[voice->sample_size], voice->samples_per_block);
|
||||||
|
ImGui::Text("Rate: %f (%d Hz)", voice->rate, (int)(48000.0/voice->rate));
|
||||||
|
ImGui::Text("EBO=%d CBO=%d LBO=%d BA=%x",
|
||||||
|
voice->ebo, voice->cbo, voice->lbo, voice->ba);
|
||||||
|
ImGui::Text("Mix: ");
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
if (i == 4) ImGui::Text(" ");
|
||||||
|
ImGui::SameLine();
|
||||||
|
char buf[64];
|
||||||
|
if (voice->vol[i] == 0xFFF) {
|
||||||
|
snprintf(buf, sizeof(buf),
|
||||||
|
"Bin %2d (MUTE) ", voice->bin[i]);
|
||||||
|
} else {
|
||||||
|
snprintf(buf, sizeof(buf),
|
||||||
|
"Bin %2d (-%.3f) ", voice->bin[i],
|
||||||
|
(float)((voice->vol[i] >> 6) & 0x3f) +
|
||||||
|
(float)((voice->vol[i] >> 0) & 0x3f) / 64.0);
|
||||||
|
}
|
||||||
|
ImGui::Text("%-17s", buf);
|
||||||
|
}
|
||||||
|
ImGui::PopFont();
|
||||||
|
ImGui::EndTooltip();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (voice_monitor >= 0) {
|
||||||
|
mcpx_apu_debug_isolate_voice(voice_monitor);
|
||||||
|
} else {
|
||||||
|
mcpx_apu_debug_clear_isolations();
|
||||||
|
}
|
||||||
|
if (voice_mute >= 0) {
|
||||||
|
mcpx_apu_debug_toggle_mute(voice_mute);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetColumnWidth(0, ImGui::GetCursorPosX());
|
||||||
|
ImGui::NextColumn();
|
||||||
|
|
||||||
|
ImGui::PushFont(g_font_mgr.m_fixed_width_font);
|
||||||
|
ImGui::Text("Frames: %04d", dbg->frames_processed);
|
||||||
|
ImGui::Text("GP Cycles: %04d", dbg->gp.cycles);
|
||||||
|
ImGui::Text("EP Cycles: %04d", dbg->ep.cycles);
|
||||||
|
bool color = (dbg->utilization > 0.9);
|
||||||
|
if (color) ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1,0,0,1));
|
||||||
|
ImGui::Text("Utilization: %.2f%%", (dbg->utilization*100));
|
||||||
|
if (color) ImGui::PopStyleColor();
|
||||||
|
ImGui::PopFont();
|
||||||
|
|
||||||
|
static int mon = 0;
|
||||||
|
mon = mcpx_apu_debug_get_monitor();
|
||||||
|
if (ImGui::Combo("Monitor", &mon, "AC97\0VP Only\0GP Only\0EP Only\0GP/EP if enabled\0")) {
|
||||||
|
mcpx_apu_debug_set_monitor(mon);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool gp_realtime;
|
||||||
|
gp_realtime = dbg->gp_realtime;
|
||||||
|
if (ImGui::Checkbox("GP Realtime\n", &gp_realtime)) {
|
||||||
|
mcpx_apu_debug_set_gp_realtime_enabled(gp_realtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ep_realtime;
|
||||||
|
ep_realtime = dbg->ep_realtime;
|
||||||
|
if (ImGui::Checkbox("EP Realtime\n", &ep_realtime)) {
|
||||||
|
mcpx_apu_debug_set_ep_realtime_enabled(ep_realtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Columns(1);
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility structure for realtime plot
|
||||||
|
struct ScrollingBuffer {
|
||||||
|
int MaxSize;
|
||||||
|
int Offset;
|
||||||
|
ImVector<ImVec2> Data;
|
||||||
|
ScrollingBuffer() {
|
||||||
|
MaxSize = 2000;
|
||||||
|
Offset = 0;
|
||||||
|
Data.reserve(MaxSize);
|
||||||
|
}
|
||||||
|
void AddPoint(float x, float y) {
|
||||||
|
if (Data.size() < MaxSize)
|
||||||
|
Data.push_back(ImVec2(x,y));
|
||||||
|
else {
|
||||||
|
Data[Offset] = ImVec2(x,y);
|
||||||
|
Offset = (Offset + 1) % MaxSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void Erase() {
|
||||||
|
if (Data.size() > 0) {
|
||||||
|
Data.shrink(0);
|
||||||
|
Offset = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
DebugVideoWindow::DebugVideoWindow()
|
||||||
|
{
|
||||||
|
m_is_open = false;
|
||||||
|
m_transparent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugVideoWindow::Draw()
|
||||||
|
{
|
||||||
|
if (!m_is_open)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float alpha = m_transparent ? 0.2 : 1.0;
|
||||||
|
PushWindowTransparencySettings(m_transparent, 0.2);
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(600.0f*g_viewport_mgr.m_scale, 150.0f*g_viewport_mgr.m_scale), ImGuiCond_Once);
|
||||||
|
if (ImGui::Begin("Video Debug", &m_is_open)) {
|
||||||
|
double x_start, x_end;
|
||||||
|
static ImPlotAxisFlags rt_axis = ImPlotAxisFlags_NoTickLabels;
|
||||||
|
ImPlot::PushStyleVar(ImPlotStyleVar_PlotPadding, ImVec2(5,5));
|
||||||
|
ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, 0.25f);
|
||||||
|
static ScrollingBuffer fps;
|
||||||
|
static float t = 0;
|
||||||
|
if (runstate_is_running()) {
|
||||||
|
t += ImGui::GetIO().DeltaTime;
|
||||||
|
fps.AddPoint(t, g_nv2a_stats.increment_fps);
|
||||||
|
}
|
||||||
|
x_start = t - 10.0;
|
||||||
|
x_end = t;
|
||||||
|
|
||||||
|
float plot_width = 0.5 * (ImGui::GetWindowSize().x -
|
||||||
|
2 * ImGui::GetStyle().WindowPadding.x -
|
||||||
|
ImGui::GetStyle().ItemSpacing.x);
|
||||||
|
|
||||||
|
ImGui::SetNextWindowBgAlpha(alpha);
|
||||||
|
if (ImPlot::BeginPlot("##ScrollingFPS", ImVec2(plot_width,75*g_viewport_mgr.m_scale))) {
|
||||||
|
ImPlot::SetupAxes(NULL, NULL, rt_axis, rt_axis | ImPlotAxisFlags_Lock);
|
||||||
|
ImPlot::SetupAxesLimits(x_start, x_end, 0, 65, ImPlotCond_Always);
|
||||||
|
if (fps.Data.size() > 0) {
|
||||||
|
ImPlot::PlotShaded("##fps", &fps.Data[0].x, &fps.Data[0].y, fps.Data.size(), 0, fps.Offset, 2 * sizeof(float));
|
||||||
|
ImPlot::PlotLine("##fps", &fps.Data[0].x, &fps.Data[0].y, fps.Data.size(), fps.Offset, 2 * sizeof(float));
|
||||||
|
}
|
||||||
|
ImPlot::Annotation(x_start, 65, ImPlot::GetLastItemColor(), ImVec2(0,0), true, "FPS: %d", g_nv2a_stats.increment_fps);
|
||||||
|
ImPlot::EndPlot();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
|
||||||
|
x_end = g_nv2a_stats.frame_count;
|
||||||
|
x_start = x_end - NV2A_PROF_NUM_FRAMES;
|
||||||
|
|
||||||
|
ImPlot::PushStyleColor(ImPlotCol_Line, ImPlot::GetColormapColor(1));
|
||||||
|
ImGui::SetNextWindowBgAlpha(alpha);
|
||||||
|
if (ImPlot::BeginPlot("##ScrollingMSPF", ImVec2(plot_width,75*g_viewport_mgr.m_scale))) {
|
||||||
|
ImPlot::SetupAxes(NULL, NULL, rt_axis, rt_axis | ImPlotAxisFlags_Lock);
|
||||||
|
ImPlot::SetupAxesLimits(x_start, x_end, 0, 100, ImPlotCond_Always);
|
||||||
|
ImPlot::PlotShaded("##mspf", &g_nv2a_stats.frame_history[0].mspf, NV2A_PROF_NUM_FRAMES, 0, 1, x_start, g_nv2a_stats.frame_ptr, sizeof(g_nv2a_stats.frame_working));
|
||||||
|
ImPlot::PlotLine("##mspf", &g_nv2a_stats.frame_history[0].mspf, NV2A_PROF_NUM_FRAMES, 1, x_start, g_nv2a_stats.frame_ptr, sizeof(g_nv2a_stats.frame_working));
|
||||||
|
ImPlot::Annotation(x_start, 100, ImPlot::GetLastItemColor(), ImVec2(0,0), true, "MSPF: %d", g_nv2a_stats.frame_history[(g_nv2a_stats.frame_ptr - 1) % NV2A_PROF_NUM_FRAMES].mspf);
|
||||||
|
ImPlot::EndPlot();
|
||||||
|
}
|
||||||
|
ImPlot::PopStyleColor();
|
||||||
|
|
||||||
|
if (ImGui::TreeNode("Advanced")) {
|
||||||
|
ImGui::SetNextWindowBgAlpha(alpha);
|
||||||
|
if (ImPlot::BeginPlot("##ScrollingDraws", ImVec2(-1,-1))) {
|
||||||
|
ImPlot::SetupAxes(NULL, NULL, rt_axis, rt_axis | ImPlotAxisFlags_Lock);
|
||||||
|
ImPlot::SetupAxesLimits(x_start, x_end, 0, 1500, ImPlotCond_Always);
|
||||||
|
for (int i = 0; i < NV2A_PROF__COUNT; i++) {
|
||||||
|
ImGui::PushID(i);
|
||||||
|
char title[64];
|
||||||
|
snprintf(title, sizeof(title), "%s: %d",
|
||||||
|
nv2a_profile_get_counter_name(i),
|
||||||
|
nv2a_profile_get_counter_value(i));
|
||||||
|
ImPlot::PushStyleColor(ImPlotCol_Line, ImPlot::GetColormapColor(i));
|
||||||
|
ImPlot::PushStyleColor(ImPlotCol_Fill, ImPlot::GetColormapColor(i));
|
||||||
|
ImPlot::PlotLine(title, &g_nv2a_stats.frame_history[0].counters[i], NV2A_PROF_NUM_FRAMES, 1, x_start, g_nv2a_stats.frame_ptr, sizeof(g_nv2a_stats.frame_working));
|
||||||
|
ImPlot::PopStyleColor(2);
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
ImPlot::EndPlot();
|
||||||
|
}
|
||||||
|
ImGui::TreePop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(2)) {
|
||||||
|
m_transparent = !m_transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImPlot::PopStyleVar(2);
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
ImGui::PopStyleColor(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
DebugApuWindow apu_window;
|
||||||
|
DebugVideoWindow video_window;
|
|
@ -0,0 +1,40 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
class DebugApuWindow
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool m_is_open;
|
||||||
|
DebugApuWindow();
|
||||||
|
void Draw();
|
||||||
|
};
|
||||||
|
|
||||||
|
class DebugVideoWindow
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool m_is_open;
|
||||||
|
bool m_transparent;
|
||||||
|
|
||||||
|
DebugVideoWindow();
|
||||||
|
void Draw();
|
||||||
|
};
|
||||||
|
|
||||||
|
extern DebugApuWindow apu_window;
|
||||||
|
extern DebugVideoWindow video_window;
|
|
@ -0,0 +1,118 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#include "font-manager.hh"
|
||||||
|
#include "viewport-manager.hh"
|
||||||
|
|
||||||
|
#include "data/Roboto-Medium.ttf.h"
|
||||||
|
#include "data/RobotoCondensed-Regular.ttf.h"
|
||||||
|
#include "data/font_awesome_6_1_1_solid.otf.h"
|
||||||
|
#include "data/abxy.ttf.h"
|
||||||
|
|
||||||
|
FontManager g_font_mgr;
|
||||||
|
|
||||||
|
FontManager::FontManager()
|
||||||
|
{
|
||||||
|
m_last_viewport_scale = 1;
|
||||||
|
m_font_scale = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FontManager::Rebuild()
|
||||||
|
{
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
|
||||||
|
// FIXME: Trim FA to only glyphs in use
|
||||||
|
|
||||||
|
io.Fonts->Clear();
|
||||||
|
|
||||||
|
{
|
||||||
|
ImFontConfig config;
|
||||||
|
config.FontDataOwnedByAtlas = false;
|
||||||
|
m_default_font = io.Fonts->AddFontFromMemoryTTF(
|
||||||
|
(void *)Roboto_Medium_data, Roboto_Medium_size,
|
||||||
|
16.0f * g_viewport_mgr.m_scale * m_font_scale, &config);
|
||||||
|
m_menu_font_small = io.Fonts->AddFontFromMemoryTTF(
|
||||||
|
(void *)RobotoCondensed_Regular_data, RobotoCondensed_Regular_size,
|
||||||
|
22.0f * g_viewport_mgr.m_scale * m_font_scale, &config);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ImFontConfig config;
|
||||||
|
config.FontDataOwnedByAtlas = false;
|
||||||
|
config.MergeMode = true;
|
||||||
|
config.GlyphOffset =
|
||||||
|
ImVec2(0, 13 * g_viewport_mgr.m_scale * m_font_scale);
|
||||||
|
config.GlyphMaxAdvanceX = 24.0f * g_viewport_mgr.m_scale * m_font_scale;
|
||||||
|
static const ImWchar icon_ranges[] = { 0xf900, 0xf903, 0 };
|
||||||
|
io.Fonts->AddFontFromMemoryTTF((void *)abxy_data, abxy_size,
|
||||||
|
40.0f * g_viewport_mgr.m_scale *
|
||||||
|
m_font_scale,
|
||||||
|
&config, icon_ranges);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ImFontConfig config;
|
||||||
|
config.FontDataOwnedByAtlas = false;
|
||||||
|
m_menu_font_medium = io.Fonts->AddFontFromMemoryTTF(
|
||||||
|
(void *)RobotoCondensed_Regular_data, RobotoCondensed_Regular_size,
|
||||||
|
26.0f * g_viewport_mgr.m_scale * m_font_scale, &config);
|
||||||
|
m_menu_font = io.Fonts->AddFontFromMemoryTTF(
|
||||||
|
(void *)RobotoCondensed_Regular_data, RobotoCondensed_Regular_size,
|
||||||
|
34.0f * g_viewport_mgr.m_scale * m_font_scale, &config);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ImFontConfig config;
|
||||||
|
config.FontDataOwnedByAtlas = false;
|
||||||
|
config.MergeMode = true;
|
||||||
|
config.GlyphOffset =
|
||||||
|
ImVec2(0, -3 * g_viewport_mgr.m_scale * m_font_scale);
|
||||||
|
config.GlyphMinAdvanceX = 32.0f * g_viewport_mgr.m_scale * m_font_scale;
|
||||||
|
static const ImWchar icon_ranges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 };
|
||||||
|
io.Fonts->AddFontFromMemoryTTF((void *)font_awesome_6_1_1_solid_data,
|
||||||
|
font_awesome_6_1_1_solid_size,
|
||||||
|
18.0f * g_viewport_mgr.m_scale *
|
||||||
|
m_font_scale,
|
||||||
|
&config, icon_ranges);
|
||||||
|
}
|
||||||
|
|
||||||
|
// {
|
||||||
|
// ImFontConfig config;
|
||||||
|
// config.FontDataOwnedByAtlas = false;
|
||||||
|
// static const ImWchar icon_ranges[] = { 0xf04c, 0xf04c, 0 };
|
||||||
|
// m_big_state_icon_font = io.Fonts->AddFontFromMemoryTTF(
|
||||||
|
// (void *)font_awesome_6_1_1_solid_data,
|
||||||
|
// font_awesome_6_1_1_solid_size,
|
||||||
|
// 64.0f * g_viewport_mgr.m_scale * m_font_scale, &config,
|
||||||
|
// icon_ranges);
|
||||||
|
// }
|
||||||
|
{
|
||||||
|
ImFontConfig config = ImFontConfig();
|
||||||
|
config.OversampleH = config.OversampleV = 1;
|
||||||
|
config.PixelSnapH = true;
|
||||||
|
config.SizePixels = 13.0f*g_viewport_mgr.m_scale;
|
||||||
|
m_fixed_width_font = io.Fonts->AddFontDefault(&config);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui_ImplOpenGL3_CreateFontsTexture();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FontManager::Update()
|
||||||
|
{
|
||||||
|
if (g_viewport_mgr.m_scale != m_last_viewport_scale) {
|
||||||
|
Rebuild();
|
||||||
|
m_last_viewport_scale = g_viewport_mgr.m_scale;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
#include "common.hh"
|
||||||
|
|
||||||
|
#include "IconsFontAwesome6.h"
|
||||||
|
#define ICON_BUTTON_A "\xef\xa4\x80"
|
||||||
|
#define ICON_BUTTON_B "\xef\xa4\x81"
|
||||||
|
#define ICON_BUTTON_X "\xef\xa4\x82"
|
||||||
|
#define ICON_BUTTON_Y "\xef\xa4\x83"
|
||||||
|
|
||||||
|
class FontManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ImFont *m_default_font;
|
||||||
|
ImFont *m_fixed_width_font;
|
||||||
|
ImFont *m_menu_font;
|
||||||
|
ImFont *m_menu_font_small;
|
||||||
|
ImFont *m_menu_font_medium;
|
||||||
|
// ImFont *m_big_state_icon_font;
|
||||||
|
float m_last_viewport_scale;
|
||||||
|
float m_font_scale;
|
||||||
|
|
||||||
|
FontManager();
|
||||||
|
void Rebuild();
|
||||||
|
void Update();
|
||||||
|
};
|
||||||
|
|
||||||
|
extern FontManager g_font_mgr;
|
|
@ -0,0 +1,777 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#include "common.hh"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <fpng.h>
|
||||||
|
#include "gl-helpers.hh"
|
||||||
|
#include "stb_image.h"
|
||||||
|
#include "data/controller_mask.png.h"
|
||||||
|
#include "data/logo_sdf.png.h"
|
||||||
|
#include "ui/shader/xemu-logo-frag.h"
|
||||||
|
#include "notifications.hh"
|
||||||
|
|
||||||
|
Fbo *controller_fbo,
|
||||||
|
*logo_fbo;
|
||||||
|
GLuint g_controller_tex,
|
||||||
|
g_logo_tex;
|
||||||
|
|
||||||
|
enum ShaderType {
|
||||||
|
Blit,
|
||||||
|
BlitGamma, // FIMXE: Move to nv2a_get_framebuffer_surface
|
||||||
|
Mask,
|
||||||
|
Logo,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct DecalShader_
|
||||||
|
{
|
||||||
|
int flip;
|
||||||
|
float scale;
|
||||||
|
uint32_t time;
|
||||||
|
GLuint prog, vao, vbo, ebo;
|
||||||
|
GLint flipy_loc;
|
||||||
|
GLint tex_loc;
|
||||||
|
GLint scale_offset_loc;
|
||||||
|
GLint tex_scale_offset_loc;
|
||||||
|
GLint color_primary_loc;
|
||||||
|
GLint color_secondary_loc;
|
||||||
|
GLint color_fill_loc;
|
||||||
|
GLint time_loc;
|
||||||
|
GLint scale_loc;
|
||||||
|
GLint palette_loc[256];
|
||||||
|
} DecalShader;
|
||||||
|
|
||||||
|
static DecalShader *g_decal_shader,
|
||||||
|
*g_logo_shader,
|
||||||
|
*g_framebuffer_shader;
|
||||||
|
|
||||||
|
GLint Fbo::vp[4];
|
||||||
|
GLint Fbo::original_fbo;
|
||||||
|
bool Fbo::blend;
|
||||||
|
|
||||||
|
DecalShader *NewDecalShader(enum ShaderType type);
|
||||||
|
void DeleteDecalShader(DecalShader *s);
|
||||||
|
|
||||||
|
static GLint GetCurrentFbo()
|
||||||
|
{
|
||||||
|
GLint fbo;
|
||||||
|
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, (GLint*)&fbo);
|
||||||
|
return fbo;
|
||||||
|
}
|
||||||
|
|
||||||
|
Fbo::Fbo(int width, int height)
|
||||||
|
{
|
||||||
|
w = width;
|
||||||
|
h = height;
|
||||||
|
|
||||||
|
// Allocate the texture
|
||||||
|
glGenTextures(1, &tex);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, tex);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA,
|
||||||
|
GL_UNSIGNED_BYTE, NULL);
|
||||||
|
|
||||||
|
GLint original = GetCurrentFbo();
|
||||||
|
|
||||||
|
// Allocate the framebuffer object
|
||||||
|
glGenFramebuffers(1, &fbo);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
||||||
|
tex, 0);
|
||||||
|
GLenum DrawBuffers[1] = { GL_COLOR_ATTACHMENT0 };
|
||||||
|
glDrawBuffers(1, DrawBuffers);
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, original);
|
||||||
|
}
|
||||||
|
|
||||||
|
Fbo::~Fbo()
|
||||||
|
{
|
||||||
|
glDeleteTextures(1, &tex);
|
||||||
|
glDeleteFramebuffers(1, &fbo);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Fbo::Target()
|
||||||
|
{
|
||||||
|
GLint vp[4];
|
||||||
|
glGetIntegerv(GL_VIEWPORT, vp);
|
||||||
|
|
||||||
|
original_fbo = GetCurrentFbo();
|
||||||
|
blend = glIsEnabled(GL_BLEND);
|
||||||
|
if (!blend) {
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
}
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||||
|
glViewport(0, 0, w, h);
|
||||||
|
glClearColor(0, 0, 0, 0);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Fbo::Restore()
|
||||||
|
{
|
||||||
|
if (!blend) {
|
||||||
|
glDisable(GL_BLEND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore default framebuffer, viewport, blending function
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, original_fbo);
|
||||||
|
glViewport(vp[0], vp[1], vp[2], vp[3]);
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
}
|
||||||
|
|
||||||
|
static GLuint InitTexture(unsigned char *data, int width, int height,
|
||||||
|
int channels)
|
||||||
|
{
|
||||||
|
GLuint tex;
|
||||||
|
glGenTextures(1, &tex);
|
||||||
|
assert(tex != 0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, tex);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
|
||||||
|
return tex;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GLuint LoadTextureFromMemory(const unsigned char *buf, unsigned int size)
|
||||||
|
{
|
||||||
|
// Flip vertically so textures are loaded according to GL convention.
|
||||||
|
stbi_set_flip_vertically_on_load(1);
|
||||||
|
|
||||||
|
int width, height, channels = 0;
|
||||||
|
unsigned char *data = stbi_load_from_memory(buf, size, &width, &height, &channels, 4);
|
||||||
|
assert(data != NULL);
|
||||||
|
|
||||||
|
GLuint tex = InitTexture(data, width, height, channels);
|
||||||
|
stbi_image_free(data);
|
||||||
|
|
||||||
|
return tex;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GLuint Shader(GLenum type, const char *src)
|
||||||
|
{
|
||||||
|
char err_buf[512];
|
||||||
|
GLuint shader = glCreateShader(type);
|
||||||
|
assert(shader && "Failed to create shader");
|
||||||
|
|
||||||
|
glShaderSource(shader, 1, &src, NULL);
|
||||||
|
glCompileShader(shader);
|
||||||
|
|
||||||
|
GLint status;
|
||||||
|
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
|
||||||
|
if (status != GL_TRUE) {
|
||||||
|
glGetShaderInfoLog(shader, sizeof(err_buf), NULL, err_buf);
|
||||||
|
fprintf(stderr, "Shader compilation failed: %s\n\n"
|
||||||
|
"[Shader Source]\n"
|
||||||
|
"%s\n", err_buf, src);
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
DecalShader *NewDecalShader(enum ShaderType type)
|
||||||
|
{
|
||||||
|
// Allocate shader wrapper object
|
||||||
|
DecalShader *s = new DecalShader;
|
||||||
|
assert(s != NULL);
|
||||||
|
s->flip = 0;
|
||||||
|
s->scale = 1.4;
|
||||||
|
s->time = 0;
|
||||||
|
|
||||||
|
const char *vert_src = R"(
|
||||||
|
#version 150 core
|
||||||
|
uniform bool in_FlipY;
|
||||||
|
uniform vec4 in_ScaleOffset;
|
||||||
|
uniform vec4 in_TexScaleOffset;
|
||||||
|
in vec2 in_Position;
|
||||||
|
in vec2 in_Texcoord;
|
||||||
|
out vec2 Texcoord;
|
||||||
|
void main() {
|
||||||
|
vec2 t = in_Texcoord;
|
||||||
|
if (in_FlipY) t.y = 1-t.y;
|
||||||
|
Texcoord = t*in_TexScaleOffset.xy + in_TexScaleOffset.zw;
|
||||||
|
gl_Position = vec4(in_Position*in_ScaleOffset.xy+in_ScaleOffset.zw, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
GLuint vert = Shader(GL_VERTEX_SHADER, vert_src);
|
||||||
|
assert(vert != 0);
|
||||||
|
|
||||||
|
// const char *image_frag_src = R"(
|
||||||
|
// #version 150 core
|
||||||
|
// uniform sampler2D tex;
|
||||||
|
// in vec2 Texcoord;
|
||||||
|
// out vec4 out_Color;
|
||||||
|
// void main() {
|
||||||
|
// out_Color.rgba = texture(tex, Texcoord);
|
||||||
|
// }
|
||||||
|
// )";
|
||||||
|
|
||||||
|
const char *image_gamma_frag_src = R"(
|
||||||
|
#version 400 core
|
||||||
|
uniform sampler2D tex;
|
||||||
|
uniform uint palette[256];
|
||||||
|
float gamma_ch(int ch, float col)
|
||||||
|
{
|
||||||
|
return float(bitfieldExtract(palette[uint(col * 255.0)], ch*8, 8)) / 255.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 gamma(vec4 col)
|
||||||
|
{
|
||||||
|
return vec4(gamma_ch(0, col.r), gamma_ch(1, col.g), gamma_ch(2, col.b), col.a);
|
||||||
|
}
|
||||||
|
in vec2 Texcoord;
|
||||||
|
out vec4 out_Color;
|
||||||
|
void main() {
|
||||||
|
out_Color.rgba = gamma(texture(tex, Texcoord));
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
// Simple 2-color decal shader
|
||||||
|
// - in_ColorFill is first pass
|
||||||
|
// - Red channel of the texture is used as primary color, mixed with 1-Red for
|
||||||
|
// secondary color.
|
||||||
|
// - Blue is a lazy alpha removal for now
|
||||||
|
// - Alpha channel passed through
|
||||||
|
const char *mask_frag_src = R"(
|
||||||
|
#version 150 core
|
||||||
|
uniform sampler2D tex;
|
||||||
|
uniform vec4 in_ColorPrimary;
|
||||||
|
uniform vec4 in_ColorSecondary;
|
||||||
|
uniform vec4 in_ColorFill;
|
||||||
|
in vec2 Texcoord;
|
||||||
|
out vec4 out_Color;
|
||||||
|
void main() {
|
||||||
|
vec4 t = texture(tex, Texcoord);
|
||||||
|
out_Color.rgba = in_ColorFill.rgba;
|
||||||
|
out_Color.rgb += mix(in_ColorSecondary.rgb, in_ColorPrimary.rgb, t.r);
|
||||||
|
out_Color.a += t.a - t.b;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
const char *frag_src = NULL;
|
||||||
|
switch (type) {
|
||||||
|
case ShaderType::Mask: frag_src = mask_frag_src; break;
|
||||||
|
// case ShaderType::Blit: frag_src = image_frag_src; break;
|
||||||
|
case ShaderType::BlitGamma: frag_src = image_gamma_frag_src; break;
|
||||||
|
case ShaderType::Logo: frag_src = xemu_logo_frag_src; break;
|
||||||
|
default: assert(0);
|
||||||
|
}
|
||||||
|
GLuint frag = Shader(GL_FRAGMENT_SHADER, frag_src);
|
||||||
|
assert(frag != 0);
|
||||||
|
|
||||||
|
// Link vertex and fragment shaders
|
||||||
|
s->prog = glCreateProgram();
|
||||||
|
glAttachShader(s->prog, vert);
|
||||||
|
glAttachShader(s->prog, frag);
|
||||||
|
glBindFragDataLocation(s->prog, 0, "out_Color");
|
||||||
|
glLinkProgram(s->prog);
|
||||||
|
glUseProgram(s->prog);
|
||||||
|
|
||||||
|
// Flag shaders for deletion when program is deleted
|
||||||
|
glDeleteShader(vert);
|
||||||
|
glDeleteShader(frag);
|
||||||
|
|
||||||
|
s->flipy_loc = glGetUniformLocation(s->prog, "in_FlipY");
|
||||||
|
s->scale_offset_loc = glGetUniformLocation(s->prog, "in_ScaleOffset");
|
||||||
|
s->tex_scale_offset_loc =
|
||||||
|
glGetUniformLocation(s->prog, "in_TexScaleOffset");
|
||||||
|
s->tex_loc = glGetUniformLocation(s->prog, "tex");
|
||||||
|
s->color_primary_loc = glGetUniformLocation(s->prog, "in_ColorPrimary");
|
||||||
|
s->color_secondary_loc = glGetUniformLocation(s->prog, "in_ColorSecondary");
|
||||||
|
s->color_fill_loc = glGetUniformLocation(s->prog, "in_ColorFill");
|
||||||
|
s->time_loc = glGetUniformLocation(s->prog, "iTime");
|
||||||
|
s->scale_loc = glGetUniformLocation(s->prog, "scale");
|
||||||
|
for (int i = 0; i < 256; i++) {
|
||||||
|
char name[64];
|
||||||
|
snprintf(name, sizeof(name), "palette[%d]", i);
|
||||||
|
s->palette_loc[i] = glGetUniformLocation(s->prog, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const GLfloat verts[6][4] = {
|
||||||
|
// x y s t
|
||||||
|
{ -1.0f, -1.0f, 0.0f, 0.0f }, // BL
|
||||||
|
{ -1.0f, 1.0f, 0.0f, 1.0f }, // TL
|
||||||
|
{ 1.0f, 1.0f, 1.0f, 1.0f }, // TR
|
||||||
|
{ 1.0f, -1.0f, 1.0f, 0.0f }, // BR
|
||||||
|
};
|
||||||
|
const GLint indicies[] = { 0, 1, 2, 3 };
|
||||||
|
|
||||||
|
glGenVertexArrays(1, &s->vao);
|
||||||
|
glBindVertexArray(s->vao);
|
||||||
|
|
||||||
|
glGenBuffers(1, &s->vbo);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, s->vbo);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_COPY);
|
||||||
|
|
||||||
|
glGenBuffers(1, &s->ebo);
|
||||||
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, s->ebo);
|
||||||
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indicies), indicies, GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
GLint loc = glGetAttribLocation(s->prog, "in_Position");
|
||||||
|
if (loc >= 0) {
|
||||||
|
glVertexAttribPointer(loc, 2, GL_FLOAT, GL_FALSE, 4*sizeof(GLfloat), (void*)0);
|
||||||
|
glEnableVertexAttribArray(loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
loc = glGetAttribLocation(s->prog, "in_Texcoord");
|
||||||
|
if (loc >= 0) {
|
||||||
|
glVertexAttribPointer(loc, 2, GL_FLOAT, GL_FALSE, 4*sizeof(GLfloat), (void*)(2*sizeof(GLfloat)));
|
||||||
|
glEnableVertexAttribArray(loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderDecal(DecalShader *s, float x, float y, float w, float h,
|
||||||
|
float tex_x, float tex_y, float tex_w, float tex_h,
|
||||||
|
uint32_t primary, uint32_t secondary, uint32_t fill)
|
||||||
|
{
|
||||||
|
GLint vp[4];
|
||||||
|
glGetIntegerv(GL_VIEWPORT, vp);
|
||||||
|
float ww = vp[2], wh = vp[3];
|
||||||
|
|
||||||
|
x = (int)x;
|
||||||
|
y = (int)y;
|
||||||
|
w = (int)w;
|
||||||
|
h = (int)h;
|
||||||
|
tex_x = (int)tex_x;
|
||||||
|
tex_y = (int)tex_y;
|
||||||
|
tex_w = (int)tex_w;
|
||||||
|
tex_h = (int)tex_h;
|
||||||
|
|
||||||
|
int tw_i, th_i;
|
||||||
|
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &tw_i);
|
||||||
|
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &th_i);
|
||||||
|
float tw = tw_i, th = th_i;
|
||||||
|
|
||||||
|
#define COL(color, c) (float)(((color)>>((c)*8)) & 0xff)/255.0
|
||||||
|
glUniform1i(s->flipy_loc, s->flip);
|
||||||
|
glUniform4f(s->scale_offset_loc, w / ww, h / wh, -1 + ((2 * x + w) / ww),
|
||||||
|
-1 + ((2 * y + h) / wh));
|
||||||
|
glUniform4f(s->tex_scale_offset_loc, tex_w / tw, tex_h / th, tex_x / tw,
|
||||||
|
tex_y / th);
|
||||||
|
glUniform1i(s->tex_loc, 0);
|
||||||
|
glUniform4f(s->color_primary_loc, COL(primary, 3), COL(primary, 2),
|
||||||
|
COL(primary, 1), COL(primary, 0));
|
||||||
|
glUniform4f(s->color_secondary_loc, COL(secondary, 3), COL(secondary, 2),
|
||||||
|
COL(secondary, 1), COL(secondary, 0));
|
||||||
|
glUniform4f(s->color_fill_loc, COL(fill, 3), COL(fill, 2), COL(fill, 1),
|
||||||
|
COL(fill, 0));
|
||||||
|
if (s->time_loc >= 0) glUniform1f(s->time_loc, s->time/1000.0f);
|
||||||
|
if (s->scale_loc >= 0) glUniform1f(s->scale_loc, s->scale);
|
||||||
|
#undef COL
|
||||||
|
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct rect {
|
||||||
|
int x, y, w, h;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct rect tex_items[] = {
|
||||||
|
{ 0, 148, 467, 364 }, // obj_controller
|
||||||
|
{ 0, 81, 67, 67 }, // obj_lstick
|
||||||
|
{ 0, 14, 67, 67 }, // obj_rstick
|
||||||
|
{ 67, 104, 68, 44 }, // obj_port_socket
|
||||||
|
{ 67, 76, 28, 28 }, // obj_port_lbl_1
|
||||||
|
{ 67, 48, 28, 28 }, // obj_port_lbl_2
|
||||||
|
{ 67, 20, 28, 28 }, // obj_port_lbl_3
|
||||||
|
{ 95, 76, 28, 28 }, // obj_port_lbl_4
|
||||||
|
};
|
||||||
|
|
||||||
|
enum tex_item_names {
|
||||||
|
obj_controller,
|
||||||
|
obj_lstick,
|
||||||
|
obj_rstick,
|
||||||
|
obj_port_socket,
|
||||||
|
obj_port_lbl_1,
|
||||||
|
obj_port_lbl_2,
|
||||||
|
obj_port_lbl_3,
|
||||||
|
obj_port_lbl_4,
|
||||||
|
};
|
||||||
|
|
||||||
|
void InitCustomRendering(void)
|
||||||
|
{
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
g_controller_tex =
|
||||||
|
LoadTextureFromMemory(controller_mask_data, controller_mask_size);
|
||||||
|
g_decal_shader = NewDecalShader(ShaderType::Mask);
|
||||||
|
controller_fbo = new Fbo(512, 512);
|
||||||
|
|
||||||
|
g_logo_tex = LoadTextureFromMemory(logo_sdf_data, logo_sdf_size);
|
||||||
|
g_logo_shader = NewDecalShader(ShaderType::Logo);
|
||||||
|
logo_fbo = new Fbo(512, 512);
|
||||||
|
|
||||||
|
g_framebuffer_shader = NewDecalShader(ShaderType::BlitGamma);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RenderMeter(DecalShader *s, float x, float y, float width,
|
||||||
|
float height, float p, uint32_t color_bg,
|
||||||
|
uint32_t color_fg)
|
||||||
|
{
|
||||||
|
RenderDecal(s, x, y, width, height, 0, 0, 1, 1, 0, 0, color_bg);
|
||||||
|
RenderDecal(s, x, y, width * p, height, 0, 0, 1, 1, 0, 0, color_fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderController(float frame_x, float frame_y, uint32_t primary_color,
|
||||||
|
uint32_t secondary_color, ControllerState *state)
|
||||||
|
{
|
||||||
|
// Location within the controller texture of masked button locations,
|
||||||
|
// relative to the origin of the controller
|
||||||
|
const struct rect jewel = { 177, 172, 113, 118 };
|
||||||
|
const struct rect lstick_ctr = { 93, 246, 0, 0 };
|
||||||
|
const struct rect rstick_ctr = { 342, 148, 0, 0 };
|
||||||
|
const struct rect buttons[12] = {
|
||||||
|
{ 367, 187, 30, 38 }, // A
|
||||||
|
{ 368, 229, 30, 38 }, // B
|
||||||
|
{ 330, 204, 30, 38 }, // X
|
||||||
|
{ 331, 247, 30, 38 }, // Y
|
||||||
|
{ 82, 121, 31, 47 }, // D-Left
|
||||||
|
{ 104, 160, 44, 25 }, // D-Up
|
||||||
|
{ 141, 121, 31, 47 }, // D-Right
|
||||||
|
{ 104, 105, 44, 25 }, // D-Down
|
||||||
|
{ 187, 94, 34, 24 }, // Back
|
||||||
|
{ 246, 94, 36, 26 }, // Start
|
||||||
|
{ 348, 288, 30, 38 }, // White
|
||||||
|
{ 386, 268, 30, 38 }, // Black
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t alpha = 0;
|
||||||
|
uint32_t now = SDL_GetTicks();
|
||||||
|
float t;
|
||||||
|
|
||||||
|
glUseProgram(g_decal_shader->prog);
|
||||||
|
glBindVertexArray(g_decal_shader->vao);
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, g_controller_tex);
|
||||||
|
|
||||||
|
// Add a 5 pixel space around the controller so we can wiggle the controller
|
||||||
|
// around to visualize rumble in action
|
||||||
|
frame_x += 5;
|
||||||
|
frame_y += 5;
|
||||||
|
float original_frame_x = frame_x;
|
||||||
|
float original_frame_y = frame_y;
|
||||||
|
|
||||||
|
// Floating point versions that will get scaled
|
||||||
|
float rumble_l = 0;
|
||||||
|
float rumble_r = 0;
|
||||||
|
|
||||||
|
glBlendEquation(GL_FUNC_ADD);
|
||||||
|
glBlendFunc(GL_ONE, GL_ZERO);
|
||||||
|
|
||||||
|
uint32_t jewel_color = secondary_color;
|
||||||
|
|
||||||
|
// Check to see if the guide button is pressed
|
||||||
|
const uint32_t animate_guide_button_duration = 2000;
|
||||||
|
if (state->buttons & CONTROLLER_BUTTON_GUIDE) {
|
||||||
|
state->animate_guide_button_end = now + animate_guide_button_duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (now < state->animate_guide_button_end) {
|
||||||
|
t = 1.0f - (float)(state->animate_guide_button_end-now)/(float)animate_guide_button_duration;
|
||||||
|
float sin_wav = (1-sin(M_PI * t / 2.0f));
|
||||||
|
|
||||||
|
// Animate guide button by highlighting logo jewel and fading out over time
|
||||||
|
alpha = sin_wav * 255.0f;
|
||||||
|
jewel_color = primary_color + alpha;
|
||||||
|
|
||||||
|
// Add a little extra flare: wiggle the frame around while we rumble
|
||||||
|
frame_x += ((float)(rand() % 5)-2.5) * (1-t);
|
||||||
|
frame_y += ((float)(rand() % 5)-2.5) * (1-t);
|
||||||
|
rumble_l = rumble_r = sin_wav;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render controller texture
|
||||||
|
RenderDecal(g_decal_shader, frame_x + 0, frame_y + 0,
|
||||||
|
tex_items[obj_controller].w, tex_items[obj_controller].h,
|
||||||
|
tex_items[obj_controller].x, tex_items[obj_controller].y,
|
||||||
|
tex_items[obj_controller].w, tex_items[obj_controller].h,
|
||||||
|
primary_color, secondary_color, 0);
|
||||||
|
|
||||||
|
glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ONE); // Blend with controller cutouts
|
||||||
|
RenderDecal(g_decal_shader, frame_x + jewel.x, frame_y + jewel.y, jewel.w,
|
||||||
|
jewel.h, 0, 0, 1, 1, 0, 0, jewel_color);
|
||||||
|
|
||||||
|
// The controller has alpha cutouts where the buttons are. Draw a surface
|
||||||
|
// behind the buttons if they are activated
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
if (state->buttons & (1 << i)) {
|
||||||
|
RenderDecal(g_decal_shader, frame_x + buttons[i].x,
|
||||||
|
frame_y + buttons[i].y, buttons[i].w, buttons[i].h, 0,
|
||||||
|
0, 1, 1, 0, 0, primary_color + 0xff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Blend with controller
|
||||||
|
|
||||||
|
// Render left thumbstick
|
||||||
|
float w = tex_items[obj_lstick].w;
|
||||||
|
float h = tex_items[obj_lstick].h;
|
||||||
|
float c_x = frame_x+lstick_ctr.x;
|
||||||
|
float c_y = frame_y+lstick_ctr.y;
|
||||||
|
float lstick_x = (float)state->axis[CONTROLLER_AXIS_LSTICK_X]/32768.0;
|
||||||
|
float lstick_y = (float)state->axis[CONTROLLER_AXIS_LSTICK_Y]/32768.0;
|
||||||
|
RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f + 10.0f * lstick_x),
|
||||||
|
(int)(c_y - h / 2.0f + 10.0f * lstick_y), w, h,
|
||||||
|
tex_items[obj_lstick].x, tex_items[obj_lstick].y, w, h,
|
||||||
|
(state->buttons & CONTROLLER_BUTTON_LSTICK) ? secondary_color :
|
||||||
|
primary_color,
|
||||||
|
(state->buttons & CONTROLLER_BUTTON_LSTICK) ? primary_color :
|
||||||
|
secondary_color,
|
||||||
|
0);
|
||||||
|
|
||||||
|
// Render right thumbstick
|
||||||
|
w = tex_items[obj_rstick].w;
|
||||||
|
h = tex_items[obj_rstick].h;
|
||||||
|
c_x = frame_x+rstick_ctr.x;
|
||||||
|
c_y = frame_y+rstick_ctr.y;
|
||||||
|
float rstick_x = (float)state->axis[CONTROLLER_AXIS_RSTICK_X]/32768.0;
|
||||||
|
float rstick_y = (float)state->axis[CONTROLLER_AXIS_RSTICK_Y]/32768.0;
|
||||||
|
RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f + 10.0f * rstick_x),
|
||||||
|
(int)(c_y - h / 2.0f + 10.0f * rstick_y), w, h,
|
||||||
|
tex_items[obj_rstick].x, tex_items[obj_rstick].y, w, h,
|
||||||
|
(state->buttons & CONTROLLER_BUTTON_RSTICK) ? secondary_color :
|
||||||
|
primary_color,
|
||||||
|
(state->buttons & CONTROLLER_BUTTON_RSTICK) ? primary_color :
|
||||||
|
secondary_color,
|
||||||
|
0);
|
||||||
|
|
||||||
|
glBlendFunc(GL_ONE, GL_ZERO); // Don't blend, just overwrite values in buffer
|
||||||
|
|
||||||
|
// Render trigger bars
|
||||||
|
float ltrig = state->axis[CONTROLLER_AXIS_LTRIG] / 32767.0;
|
||||||
|
float rtrig = state->axis[CONTROLLER_AXIS_RTRIG] / 32767.0;
|
||||||
|
const uint32_t animate_trigger_duration = 1000;
|
||||||
|
if ((ltrig > 0) || (rtrig > 0)) {
|
||||||
|
state->animate_trigger_end = now + animate_trigger_duration;
|
||||||
|
rumble_l = fmax(rumble_l, ltrig);
|
||||||
|
rumble_r = fmax(rumble_r, rtrig);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animate trigger alpha down after a period of inactivity
|
||||||
|
alpha = 0x80;
|
||||||
|
if (state->animate_trigger_end > now) {
|
||||||
|
t = 1.0f - (float)(state->animate_trigger_end-now)/(float)animate_trigger_duration;
|
||||||
|
float sin_wav = (1-sin(M_PI * t / 2.0f));
|
||||||
|
alpha += fmin(sin_wav * 0x40, 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderMeter(g_decal_shader, original_frame_x + 10,
|
||||||
|
original_frame_y + tex_items[obj_controller].h + 20, 150, 5,
|
||||||
|
ltrig, primary_color + alpha, primary_color + 0xff);
|
||||||
|
RenderMeter(g_decal_shader,
|
||||||
|
original_frame_x + tex_items[obj_controller].w - 160,
|
||||||
|
original_frame_y + tex_items[obj_controller].h + 20, 150, 5,
|
||||||
|
rtrig, primary_color + alpha, primary_color + 0xff);
|
||||||
|
|
||||||
|
// Apply rumble updates
|
||||||
|
state->rumble_l = (int)(rumble_l * (float)0xffff);
|
||||||
|
state->rumble_r = (int)(rumble_r * (float)0xffff);
|
||||||
|
|
||||||
|
glBindVertexArray(0);
|
||||||
|
glUseProgram(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderControllerPort(float frame_x, float frame_y, int i,
|
||||||
|
uint32_t port_color)
|
||||||
|
{
|
||||||
|
glUseProgram(g_decal_shader->prog);
|
||||||
|
glBindVertexArray(g_decal_shader->vao);
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, g_controller_tex);
|
||||||
|
glBlendFunc(GL_ONE, GL_ZERO);
|
||||||
|
|
||||||
|
// Render port socket
|
||||||
|
RenderDecal(g_decal_shader, frame_x, frame_y, tex_items[obj_port_socket].w,
|
||||||
|
tex_items[obj_port_socket].h, tex_items[obj_port_socket].x,
|
||||||
|
tex_items[obj_port_socket].y, tex_items[obj_port_socket].w,
|
||||||
|
tex_items[obj_port_socket].h, port_color, port_color, 0);
|
||||||
|
|
||||||
|
frame_x += (tex_items[obj_port_socket].w-tex_items[obj_port_lbl_1].w)/2;
|
||||||
|
frame_y += tex_items[obj_port_socket].h + 8;
|
||||||
|
|
||||||
|
// Render port label
|
||||||
|
RenderDecal(
|
||||||
|
g_decal_shader, frame_x, frame_y, tex_items[obj_port_lbl_1 + i].w,
|
||||||
|
tex_items[obj_port_lbl_1 + i].h, tex_items[obj_port_lbl_1 + i].x,
|
||||||
|
tex_items[obj_port_lbl_1 + i].y, tex_items[obj_port_lbl_1 + i].w,
|
||||||
|
tex_items[obj_port_lbl_1 + i].h, port_color, port_color, 0);
|
||||||
|
|
||||||
|
glBindVertexArray(0);
|
||||||
|
glUseProgram(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderLogo(uint32_t time, uint32_t primary_color, uint32_t secondary_color,
|
||||||
|
uint32_t fill_color)
|
||||||
|
{
|
||||||
|
g_logo_shader->time = time;
|
||||||
|
glUseProgram(g_logo_shader->prog);
|
||||||
|
glBindVertexArray(g_decal_shader->vao);
|
||||||
|
glBlendFunc(GL_ONE, GL_ZERO);
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, g_logo_tex);
|
||||||
|
RenderDecal(g_logo_shader, 0, 0, 512, 512, 0, 0, 128, 128, primary_color,
|
||||||
|
secondary_color, fill_color);
|
||||||
|
glBindVertexArray(0);
|
||||||
|
glUseProgram(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderFramebuffer(GLint tex, int width, int height, bool flip)
|
||||||
|
{
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, tex);
|
||||||
|
|
||||||
|
int tw, th;
|
||||||
|
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &tw);
|
||||||
|
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &th);
|
||||||
|
|
||||||
|
// Calculate scaling factors
|
||||||
|
float scale[2];
|
||||||
|
if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_STRETCH) {
|
||||||
|
// Stretch to fit
|
||||||
|
scale[0] = 1.0;
|
||||||
|
scale[1] = 1.0;
|
||||||
|
} else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_CENTER) {
|
||||||
|
// Centered
|
||||||
|
scale[0] = (float)tw/(float)width;
|
||||||
|
scale[1] = (float)th/(float)height;
|
||||||
|
} else {
|
||||||
|
float t_ratio;
|
||||||
|
if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_16_9) {
|
||||||
|
// Scale to fit window using a fixed 16:9 aspect ratio
|
||||||
|
t_ratio = 16.0f/9.0f;
|
||||||
|
} else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_4_3) {
|
||||||
|
t_ratio = 4.0f/3.0f;
|
||||||
|
} else {
|
||||||
|
// Scale to fit, preserving framebuffer aspect ratio
|
||||||
|
t_ratio = (float)tw/(float)th;
|
||||||
|
}
|
||||||
|
|
||||||
|
float w_ratio = (float)width/(float)height;
|
||||||
|
if (w_ratio >= t_ratio) {
|
||||||
|
scale[0] = t_ratio/w_ratio;
|
||||||
|
scale[1] = 1.0;
|
||||||
|
} else {
|
||||||
|
scale[0] = 1.0;
|
||||||
|
scale[1] = w_ratio/t_ratio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DecalShader *s = g_framebuffer_shader;
|
||||||
|
s->flip = flip;
|
||||||
|
glViewport(0, 0, width, height);
|
||||||
|
glUseProgram(s->prog);
|
||||||
|
glBindVertexArray(s->vao);
|
||||||
|
glUniform1i(s->flipy_loc, s->flip);
|
||||||
|
glUniform4f(s->scale_offset_loc, scale[0], scale[1], 0, 0);
|
||||||
|
glUniform4f(s->tex_scale_offset_loc, 1.0, 1.0, 0, 0);
|
||||||
|
glUniform1i(s->tex_loc, 0);
|
||||||
|
|
||||||
|
const uint8_t *palette = nv2a_get_dac_palette();
|
||||||
|
for (int i = 0; i < 256; i++) {
|
||||||
|
uint32_t e = (palette[i * 3 + 2] << 16) | (palette[i * 3 + 1] << 8) |
|
||||||
|
palette[i * 3];
|
||||||
|
glUniform1ui(s->palette_loc[i], e);
|
||||||
|
}
|
||||||
|
|
||||||
|
glClearColor(0, 0, 0, 0);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SaveScreenshot(GLuint tex, bool flip)
|
||||||
|
{
|
||||||
|
int width, height;
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, tex);
|
||||||
|
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
|
||||||
|
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
|
if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_16_9) {
|
||||||
|
width = height * (16.0f / 9.0f);
|
||||||
|
} else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_4_3) {
|
||||||
|
width = height * (4.0f / 3.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> pixels;
|
||||||
|
pixels.resize(width * height * 4);
|
||||||
|
|
||||||
|
Fbo fbo(width, height);
|
||||||
|
fbo.Target();
|
||||||
|
bool blend = glIsEnabled(GL_BLEND);
|
||||||
|
if (blend) glDisable(GL_BLEND);
|
||||||
|
RenderFramebuffer(tex, width, height, !flip);
|
||||||
|
if (blend) glEnable(GL_BLEND);
|
||||||
|
glPixelStorei(GL_PACK_ROW_LENGTH, width);
|
||||||
|
glPixelStorei(GL_PACK_IMAGE_HEIGHT, height);
|
||||||
|
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||||
|
glReadnPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels.size(),
|
||||||
|
pixels.data());
|
||||||
|
fbo.Restore();
|
||||||
|
|
||||||
|
char fname[128];
|
||||||
|
Error *err = NULL;
|
||||||
|
std::vector<uint8_t> png;
|
||||||
|
if (fpng::fpng_encode_image_to_memory(pixels.data(), width, height, 3, png)) {
|
||||||
|
time_t t = time(NULL);
|
||||||
|
struct tm *tmp = localtime(&t);
|
||||||
|
if (tmp) {
|
||||||
|
strftime(fname, sizeof(fname), "xemu-%Y-%m-%d-%H-%M-%S.png",
|
||||||
|
tmp);
|
||||||
|
} else {
|
||||||
|
strcpy(fname, "xemu.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *output_dir = g_config.general.screenshot_dir;
|
||||||
|
if (!strlen(output_dir)) {
|
||||||
|
output_dir = ".";
|
||||||
|
}
|
||||||
|
// FIXME: Check for existing path
|
||||||
|
char *path = g_strdup_printf("%s/%s", output_dir, fname);
|
||||||
|
FILE *fd = qemu_fopen(path, "wb");
|
||||||
|
if (fd) {
|
||||||
|
int s = fwrite(png.data(), png.size(), 1, fd);
|
||||||
|
if (s != 1) {
|
||||||
|
error_setg(&err, "Failed to write %s", path);
|
||||||
|
}
|
||||||
|
fclose(fd);
|
||||||
|
} else {
|
||||||
|
error_setg(&err, "Failed to open %s for writing", path);
|
||||||
|
}
|
||||||
|
g_free(path);
|
||||||
|
} else {
|
||||||
|
error_setg(&err, "Failed to encode PNG image");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
xemu_queue_error_message(error_get_pretty(err));
|
||||||
|
error_report_err(err);
|
||||||
|
} else {
|
||||||
|
char *msg = g_strdup_printf("Screenshot Saved: %s", fname);
|
||||||
|
xemu_queue_notification(msg);
|
||||||
|
free(msg);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
#include "common.hh"
|
||||||
|
#include "../xemu-input.h"
|
||||||
|
|
||||||
|
class Fbo
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static GLint vp[4];
|
||||||
|
static GLint original_fbo;
|
||||||
|
static bool blend;
|
||||||
|
|
||||||
|
int w, h;
|
||||||
|
GLuint fbo, tex;
|
||||||
|
|
||||||
|
Fbo(int width, int height);
|
||||||
|
~Fbo();
|
||||||
|
inline GLuint Texture() { return tex; }
|
||||||
|
void Target();
|
||||||
|
void Restore();
|
||||||
|
};
|
||||||
|
|
||||||
|
extern Fbo *controller_fbo, *logo_fbo;
|
||||||
|
|
||||||
|
void InitCustomRendering(void);
|
||||||
|
void RenderLogo(uint32_t time, uint32_t primary_color, uint32_t secondary_color,
|
||||||
|
uint32_t fill_color);
|
||||||
|
void RenderController(float frame_x, float frame_y, uint32_t primary_color,
|
||||||
|
uint32_t secondary_color, ControllerState *state);
|
||||||
|
void RenderControllerPort(float frame_x, float frame_y, int i,
|
||||||
|
uint32_t port_color);
|
||||||
|
void RenderFramebuffer(GLint tex, int width, int height, bool flip);
|
||||||
|
void SaveScreenshot(GLuint tex, bool flip);
|
|
@ -0,0 +1,116 @@
|
||||||
|
#include "common.hh"
|
||||||
|
#include "input-manager.hh"
|
||||||
|
#include "../xemu-input.h"
|
||||||
|
|
||||||
|
InputManager g_input_mgr;
|
||||||
|
|
||||||
|
InputManager::InputManager()
|
||||||
|
{
|
||||||
|
m_last_mouse_pos = ImVec2(0, 0);
|
||||||
|
m_navigating_with_controller = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputManager::Update()
|
||||||
|
{
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
|
||||||
|
// Combine all controller states to allow any controller to navigate
|
||||||
|
m_buttons = 0;
|
||||||
|
int16_t axis[CONTROLLER_AXIS__COUNT] = {0};
|
||||||
|
|
||||||
|
ControllerState *iter;
|
||||||
|
QTAILQ_FOREACH(iter, &available_controllers, entry) {
|
||||||
|
if (iter->type != INPUT_DEVICE_SDL_GAMECONTROLLER) continue;
|
||||||
|
m_buttons |= iter->buttons;
|
||||||
|
// We simply take any axis that is >10 % activation
|
||||||
|
for (int i = 0; i < CONTROLLER_AXIS__COUNT; i++) {
|
||||||
|
if ((iter->axis[i] > 3276) || (iter->axis[i] < -3276)) {
|
||||||
|
axis[i] = iter->axis[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the mouse is moved, wake the ui
|
||||||
|
ImVec2 current_mouse_pos = ImGui::GetMousePos();
|
||||||
|
m_mouse_moved = false;
|
||||||
|
if ((current_mouse_pos.x != m_last_mouse_pos.x) ||
|
||||||
|
(current_mouse_pos.y != m_last_mouse_pos.y) ||
|
||||||
|
ImGui::IsMouseDown(0) || ImGui::IsMouseDown(1) || ImGui::IsMouseDown(2)) {
|
||||||
|
m_mouse_moved = true;
|
||||||
|
m_last_mouse_pos = current_mouse_pos;
|
||||||
|
m_navigating_with_controller = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If mouse capturing is enabled (we are in a dialog), ensure the UI is alive
|
||||||
|
bool controller_focus_capture = false;
|
||||||
|
if (io.NavActive) {
|
||||||
|
controller_focus_capture = true;
|
||||||
|
m_navigating_with_controller |= !!m_buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Prevent controller events from going to the guest if they are being used
|
||||||
|
// to navigate the HUD
|
||||||
|
xemu_input_set_test_mode(controller_focus_capture); // FIXME: Rename 'test mode'
|
||||||
|
|
||||||
|
// Update gamepad inputs
|
||||||
|
#define IM_SATURATE(V) (V < 0.0f ? 0.0f : V > 1.0f ? 1.0f : V)
|
||||||
|
#define MAP_BUTTON(KEY_NO, BUTTON_NO) { io.AddKeyEvent(KEY_NO, !!(m_buttons & BUTTON_NO)); }
|
||||||
|
#define MAP_ANALOG(KEY_NO, AXIS_NO, V0, V1) { float vn = (float)(axis[AXIS_NO] - V0) / (float)(V1 - V0); vn = IM_SATURATE(vn); io.AddKeyAnalogEvent(KEY_NO, vn > 0.1f, vn); }
|
||||||
|
const int thumb_dead_zone = 8000; // SDL_gamecontroller.h suggests using this value.
|
||||||
|
MAP_BUTTON(ImGuiKey_GamepadStart, CONTROLLER_BUTTON_START);
|
||||||
|
MAP_BUTTON(ImGuiKey_GamepadBack, CONTROLLER_BUTTON_BACK);
|
||||||
|
MAP_BUTTON(ImGuiKey_GamepadFaceDown, CONTROLLER_BUTTON_A); // Xbox A, PS Cross
|
||||||
|
MAP_BUTTON(ImGuiKey_GamepadFaceRight, CONTROLLER_BUTTON_B); // Xbox B, PS Circle
|
||||||
|
MAP_BUTTON(ImGuiKey_GamepadFaceLeft, CONTROLLER_BUTTON_X); // Xbox X, PS Square
|
||||||
|
MAP_BUTTON(ImGuiKey_GamepadFaceUp, CONTROLLER_BUTTON_Y); // Xbox Y, PS Triangle
|
||||||
|
MAP_BUTTON(ImGuiKey_GamepadDpadLeft, CONTROLLER_BUTTON_DPAD_LEFT);
|
||||||
|
MAP_BUTTON(ImGuiKey_GamepadDpadRight, CONTROLLER_BUTTON_DPAD_RIGHT);
|
||||||
|
MAP_BUTTON(ImGuiKey_GamepadDpadUp, CONTROLLER_BUTTON_DPAD_UP);
|
||||||
|
MAP_BUTTON(ImGuiKey_GamepadDpadDown, CONTROLLER_BUTTON_DPAD_DOWN);
|
||||||
|
MAP_BUTTON(ImGuiKey_GamepadL1, CONTROLLER_BUTTON_WHITE);
|
||||||
|
MAP_BUTTON(ImGuiKey_GamepadR1, CONTROLLER_BUTTON_BLACK);
|
||||||
|
//MAP_ANALOG(ImGuiKey_GamepadL2, SDL_CONTROLLER_AXIS_TRIGGERLEFT, 0.0f, 32767);
|
||||||
|
//MAP_ANALOG(ImGuiKey_GamepadR2, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, 0.0f, 32767);
|
||||||
|
//MAP_BUTTON(ImGuiKey_GamepadL3, SDL_CONTROLLER_BUTTON_LEFTSTICK);
|
||||||
|
//MAP_BUTTON(ImGuiKey_GamepadR3, SDL_CONTROLLER_BUTTON_RIGHTSTICK);
|
||||||
|
MAP_ANALOG(ImGuiKey_GamepadLStickLeft, CONTROLLER_AXIS_LSTICK_X, -thumb_dead_zone, -32768);
|
||||||
|
MAP_ANALOG(ImGuiKey_GamepadLStickRight, CONTROLLER_AXIS_LSTICK_X, +thumb_dead_zone, +32767);
|
||||||
|
MAP_ANALOG(ImGuiKey_GamepadLStickUp, CONTROLLER_AXIS_LSTICK_Y, +thumb_dead_zone, +32768);
|
||||||
|
MAP_ANALOG(ImGuiKey_GamepadLStickDown, CONTROLLER_AXIS_LSTICK_Y, -thumb_dead_zone, -32767);
|
||||||
|
MAP_ANALOG(ImGuiKey_GamepadRStickLeft, CONTROLLER_AXIS_RSTICK_X, -thumb_dead_zone, -32768);
|
||||||
|
MAP_ANALOG(ImGuiKey_GamepadRStickRight, CONTROLLER_AXIS_RSTICK_X, +thumb_dead_zone, +32767);
|
||||||
|
MAP_ANALOG(ImGuiKey_GamepadRStickUp, CONTROLLER_AXIS_RSTICK_Y, +thumb_dead_zone, +32768);
|
||||||
|
MAP_ANALOG(ImGuiKey_GamepadRStickDown, CONTROLLER_AXIS_RSTICK_Y, -thumb_dead_zone, -32767);
|
||||||
|
#undef MAP_BUTTON
|
||||||
|
#undef MAP_ANALOG
|
||||||
|
#undef IM_SATURATE
|
||||||
|
|
||||||
|
io.BackendUsingLegacyNavInputArray = true;
|
||||||
|
|
||||||
|
// Map to nav inputs
|
||||||
|
#define NAV_MAP_KEY(_KEY, _NAV_INPUT, _ACTIVATE_NAV) \
|
||||||
|
do { \
|
||||||
|
io.NavInputs[_NAV_INPUT] = io.KeysData[_KEY - ImGuiKey_KeysData_OFFSET].AnalogValue; \
|
||||||
|
if (_ACTIVATE_NAV && io.NavInputs[_NAV_INPUT] > 0.0f) { \
|
||||||
|
ImGui::GetCurrentContext()->NavInputSource = ImGuiInputSource_Gamepad; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
NAV_MAP_KEY(ImGuiKey_GamepadFaceDown, ImGuiNavInput_Activate, true);
|
||||||
|
NAV_MAP_KEY(ImGuiKey_GamepadFaceRight, ImGuiNavInput_Cancel, true);
|
||||||
|
//NAV_MAP_KEY(ImGuiKey_Menu, ImGuiNavInput_Menu, true);
|
||||||
|
NAV_MAP_KEY(ImGuiKey_GamepadFaceUp, ImGuiNavInput_Input, true);
|
||||||
|
NAV_MAP_KEY(ImGuiKey_GamepadDpadLeft, ImGuiNavInput_DpadLeft, true);
|
||||||
|
NAV_MAP_KEY(ImGuiKey_GamepadDpadRight, ImGuiNavInput_DpadRight, true);
|
||||||
|
NAV_MAP_KEY(ImGuiKey_GamepadDpadUp, ImGuiNavInput_DpadUp, true);
|
||||||
|
NAV_MAP_KEY(ImGuiKey_GamepadDpadDown, ImGuiNavInput_DpadDown, true);
|
||||||
|
NAV_MAP_KEY(ImGuiKey_GamepadL1, ImGuiNavInput_FocusPrev, false);
|
||||||
|
NAV_MAP_KEY(ImGuiKey_GamepadR1, ImGuiNavInput_FocusNext, false);
|
||||||
|
NAV_MAP_KEY(ImGuiKey_GamepadL1, ImGuiNavInput_TweakSlow, false);
|
||||||
|
NAV_MAP_KEY(ImGuiKey_GamepadR1, ImGuiNavInput_TweakFast, false);
|
||||||
|
NAV_MAP_KEY(ImGuiKey_GamepadLStickLeft, ImGuiNavInput_LStickLeft, false);
|
||||||
|
NAV_MAP_KEY(ImGuiKey_GamepadLStickRight, ImGuiNavInput_LStickRight, false);
|
||||||
|
NAV_MAP_KEY(ImGuiKey_GamepadLStickUp, ImGuiNavInput_LStickUp, false);
|
||||||
|
NAV_MAP_KEY(ImGuiKey_GamepadLStickDown, ImGuiNavInput_LStickDown, false);
|
||||||
|
#undef NAV_MAP_KEY
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
#include "common.hh"
|
||||||
|
|
||||||
|
class InputManager
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
ImVec2 m_last_mouse_pos;
|
||||||
|
bool m_navigating_with_controller;
|
||||||
|
uint32_t m_buttons;
|
||||||
|
bool m_mouse_moved;
|
||||||
|
|
||||||
|
public:
|
||||||
|
InputManager();
|
||||||
|
void Update();
|
||||||
|
inline bool IsNavigatingWithController() { return m_navigating_with_controller; }
|
||||||
|
inline bool MouseMoved() { return m_mouse_moved; }
|
||||||
|
inline uint32_t CombinedButtons() { return m_buttons; }
|
||||||
|
};
|
||||||
|
|
||||||
|
extern InputManager g_input_mgr;
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,187 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include "common.hh"
|
||||||
|
#include "widgets.hh"
|
||||||
|
#include "scene.hh"
|
||||||
|
#include "scene-components.hh"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "net/pcap.h"
|
||||||
|
#undef snprintf // FIXME
|
||||||
|
}
|
||||||
|
|
||||||
|
class MainMenuTabView
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~MainMenuTabView();
|
||||||
|
virtual void Draw();
|
||||||
|
};
|
||||||
|
|
||||||
|
class MainMenuGeneralView : public virtual MainMenuTabView
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void Draw() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MainMenuInputView : public virtual MainMenuTabView
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void Draw() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MainMenuDisplayView : public virtual MainMenuTabView
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void Draw() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MainMenuAudioView : public virtual MainMenuTabView
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void Draw() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class NetworkInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::string m_pcap_name;
|
||||||
|
std::string m_description;
|
||||||
|
std::string m_friendly_name;
|
||||||
|
|
||||||
|
NetworkInterface(pcap_if_t *pcap_desc, char *_friendlyname = NULL);
|
||||||
|
};
|
||||||
|
|
||||||
|
class NetworkInterfaceManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::vector<std::unique_ptr<NetworkInterface>> m_ifaces;
|
||||||
|
NetworkInterface *m_current_iface;
|
||||||
|
bool m_failed_to_load_lib;
|
||||||
|
|
||||||
|
NetworkInterfaceManager();
|
||||||
|
void Refresh(void);
|
||||||
|
void Select(NetworkInterface &iface);
|
||||||
|
bool IsCurrent(NetworkInterface &iface);
|
||||||
|
};
|
||||||
|
|
||||||
|
class MainMenuNetworkView : public virtual MainMenuTabView
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
char remote_addr[64];
|
||||||
|
char local_addr[64];
|
||||||
|
bool should_refresh;
|
||||||
|
std::unique_ptr<NetworkInterfaceManager> iface_mgr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
MainMenuNetworkView();
|
||||||
|
void Draw() override;
|
||||||
|
void DrawPcapOptions(bool appearing);
|
||||||
|
void DrawNatOptions(bool appearing);
|
||||||
|
void DrawUdpOptions(bool appearing);
|
||||||
|
};
|
||||||
|
|
||||||
|
class MainMenuSnapshotsView : public virtual MainMenuTabView
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void SnapshotBigButton(const char *name, const char *title_name,
|
||||||
|
GLuint screenshot);
|
||||||
|
void Draw() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MainMenuSystemView : public virtual MainMenuTabView
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
bool m_dirty;
|
||||||
|
|
||||||
|
public:
|
||||||
|
MainMenuSystemView();
|
||||||
|
void Draw() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MainMenuAboutView : public virtual MainMenuTabView
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void Draw() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MainMenuTabButton
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
std::string m_icon;
|
||||||
|
std::string m_text;
|
||||||
|
MainMenuTabView *m_view;
|
||||||
|
|
||||||
|
public:
|
||||||
|
MainMenuTabButton(std::string text, std::string icon = "", MainMenuTabView *view = nullptr);
|
||||||
|
MainMenuTabView *view();
|
||||||
|
bool Draw(bool selected);
|
||||||
|
};
|
||||||
|
|
||||||
|
class MainMenuScene : public virtual Scene {
|
||||||
|
protected:
|
||||||
|
EasingAnimation m_animation;
|
||||||
|
bool m_focus_view;
|
||||||
|
bool m_had_focus_last_frame;
|
||||||
|
int m_current_view_index;
|
||||||
|
int m_next_view_index;
|
||||||
|
BackgroundGradient m_background;
|
||||||
|
NavControlAnnotation m_nav_control_view;
|
||||||
|
std::vector<MainMenuTabButton*> m_tabs;
|
||||||
|
MainMenuTabButton m_general_button,
|
||||||
|
m_input_button,
|
||||||
|
m_display_button,
|
||||||
|
m_audio_button,
|
||||||
|
m_network_button,
|
||||||
|
// m_snapshots_button,
|
||||||
|
m_system_button,
|
||||||
|
m_about_button;
|
||||||
|
MainMenuGeneralView m_general_view;
|
||||||
|
MainMenuInputView m_input_view;
|
||||||
|
MainMenuDisplayView m_display_view;
|
||||||
|
MainMenuAudioView m_audio_view;
|
||||||
|
MainMenuNetworkView m_network_view;
|
||||||
|
// MainMenuSnapshotsView m_snapshots_view;
|
||||||
|
MainMenuSystemView m_system_view;
|
||||||
|
MainMenuAboutView m_about_view;
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
MainMenuScene();
|
||||||
|
void ShowGeneral();
|
||||||
|
void ShowInput();
|
||||||
|
void ShowDisplay();
|
||||||
|
void ShowAudio();
|
||||||
|
void ShowNetwork();
|
||||||
|
// void ShowSnapshots();
|
||||||
|
void ShowSystem();
|
||||||
|
void ShowAbout();
|
||||||
|
void SetNextViewIndexWithFocus(int i);
|
||||||
|
void Show() override;
|
||||||
|
void Hide() override;
|
||||||
|
bool IsAnimating() override;
|
||||||
|
void SetNextViewIndex(int i);
|
||||||
|
void HandleInput();
|
||||||
|
bool Draw() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern MainMenuScene g_main_menu;
|
|
@ -0,0 +1,306 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <epoxy/gl.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <fpng.h>
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "common.hh"
|
||||||
|
#include "xemu-hud.h"
|
||||||
|
#include "misc.hh"
|
||||||
|
#include "gl-helpers.hh"
|
||||||
|
#include "input-manager.hh"
|
||||||
|
#include "viewport-manager.hh"
|
||||||
|
#include "font-manager.hh"
|
||||||
|
#include "scene.hh"
|
||||||
|
#include "scene-manager.hh"
|
||||||
|
#include "main-menu.hh"
|
||||||
|
#include "popup-menu.hh"
|
||||||
|
#include "notifications.hh"
|
||||||
|
#include "monitor.hh"
|
||||||
|
#include "debug.hh"
|
||||||
|
#include "welcome.hh"
|
||||||
|
#include "menubar.hh"
|
||||||
|
#include "compat.hh"
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#include "update.hh"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool g_screenshot_pending;
|
||||||
|
float g_main_menu_height;
|
||||||
|
|
||||||
|
static ImGuiStyle g_base_style;
|
||||||
|
static SDL_Window *g_sdl_window;
|
||||||
|
static float g_last_scale;
|
||||||
|
static int g_vsync;
|
||||||
|
static GLuint g_tex;
|
||||||
|
static bool g_flip_req;
|
||||||
|
|
||||||
|
|
||||||
|
static void InitializeStyle()
|
||||||
|
{
|
||||||
|
g_font_mgr.Rebuild();
|
||||||
|
|
||||||
|
ImGui::StyleColorsDark();
|
||||||
|
ImVec4 *c = ImGui::GetStyle().Colors;
|
||||||
|
c[ImGuiCol_Text] = ImVec4(0.94f, 0.94f, 0.94f, 1.00f);
|
||||||
|
c[ImGuiCol_TextDisabled] = ImVec4(0.86f, 0.93f, 0.89f, 0.28f);
|
||||||
|
c[ImGuiCol_WindowBg] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f);
|
||||||
|
c[ImGuiCol_ChildBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.98f);
|
||||||
|
c[ImGuiCol_PopupBg] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f);
|
||||||
|
c[ImGuiCol_Border] = ImVec4(0.11f, 0.11f, 0.11f, 0.60f);
|
||||||
|
c[ImGuiCol_BorderShadow] = ImVec4(0.16f, 0.16f, 0.16f, 0.00f);
|
||||||
|
c[ImGuiCol_FrameBg] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f);
|
||||||
|
c[ImGuiCol_FrameBgHovered] = ImVec4(0.30f, 0.30f, 0.30f, 1.00f);
|
||||||
|
c[ImGuiCol_FrameBgActive] = ImVec4(0.28f, 0.71f, 0.25f, 1.00f);
|
||||||
|
c[ImGuiCol_TitleBg] = ImVec4(0.20f, 0.51f, 0.18f, 1.00f);
|
||||||
|
c[ImGuiCol_TitleBgActive] = ImVec4(0.26f, 0.66f, 0.23f, 1.00f);
|
||||||
|
c[ImGuiCol_TitleBgCollapsed] = ImVec4(0.16f, 0.16f, 0.16f, 0.75f);
|
||||||
|
c[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 0.00f);
|
||||||
|
c[ImGuiCol_ScrollbarBg] = ImVec4(0.16f, 0.16f, 0.16f, 0.00f);
|
||||||
|
c[ImGuiCol_ScrollbarGrab] = ImVec4(0.30f, 0.30f, 0.30f, 1.00f);
|
||||||
|
c[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.24f, 0.60f, 0.00f, 1.00f);
|
||||||
|
c[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.24f, 0.60f, 0.00f, 1.00f);
|
||||||
|
c[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.66f, 0.23f, 1.00f);
|
||||||
|
c[ImGuiCol_SliderGrab] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
|
||||||
|
c[ImGuiCol_SliderGrabActive] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
|
||||||
|
c[ImGuiCol_Button] = ImVec4(0.17f, 0.17f, 0.17f, 1.00f);
|
||||||
|
c[ImGuiCol_ButtonHovered] = ImVec4(0.24f, 0.60f, 0.00f, 1.00f);
|
||||||
|
c[ImGuiCol_ButtonActive] = ImVec4(0.26f, 0.66f, 0.23f, 1.00f);
|
||||||
|
c[ImGuiCol_Header] = ImVec4(0.24f, 0.60f, 0.00f, 1.00f);
|
||||||
|
c[ImGuiCol_HeaderHovered] = ImVec4(0.24f, 0.60f, 0.00f, 1.00f);
|
||||||
|
c[ImGuiCol_HeaderActive] = ImVec4(0.24f, 0.60f, 0.00f, 1.00f);
|
||||||
|
c[ImGuiCol_Separator] = ImVec4(1.00f, 1.00f, 1.00f, 0.25f);
|
||||||
|
c[ImGuiCol_SeparatorHovered] = ImVec4(0.13f, 0.87f, 0.16f, 0.78f);
|
||||||
|
c[ImGuiCol_SeparatorActive] = ImVec4(0.25f, 0.75f, 0.10f, 1.00f);
|
||||||
|
c[ImGuiCol_ResizeGrip] = ImVec4(0.47f, 0.83f, 0.49f, 0.04f);
|
||||||
|
c[ImGuiCol_ResizeGripHovered] = ImVec4(0.28f, 0.71f, 0.25f, 0.78f);
|
||||||
|
c[ImGuiCol_ResizeGripActive] = ImVec4(0.28f, 0.71f, 0.25f, 1.00f);
|
||||||
|
c[ImGuiCol_Tab] = ImVec4(0.26f, 0.67f, 0.23f, 0.95f);
|
||||||
|
c[ImGuiCol_TabHovered] = ImVec4(0.24f, 0.60f, 0.00f, 1.00f);
|
||||||
|
c[ImGuiCol_TabActive] = ImVec4(0.24f, 0.60f, 0.00f, 1.00f);
|
||||||
|
c[ImGuiCol_TabUnfocused] = ImVec4(0.21f, 0.54f, 0.19f, 0.99f);
|
||||||
|
c[ImGuiCol_TabUnfocusedActive] = ImVec4(0.24f, 0.60f, 0.21f, 1.00f);
|
||||||
|
c[ImGuiCol_PlotLines] = ImVec4(0.86f, 0.93f, 0.89f, 0.63f);
|
||||||
|
c[ImGuiCol_PlotLinesHovered] = ImVec4(0.28f, 0.71f, 0.25f, 1.00f);
|
||||||
|
c[ImGuiCol_PlotHistogram] = ImVec4(0.86f, 0.93f, 0.89f, 0.63f);
|
||||||
|
c[ImGuiCol_PlotHistogramHovered] = ImVec4(0.28f, 0.71f, 0.25f, 1.00f);
|
||||||
|
c[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.66f, 0.23f, 1.00f);
|
||||||
|
c[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
|
||||||
|
c[ImGuiCol_NavHighlight] = ImVec4(0.28f, 0.71f, 0.25f, 1.00f);
|
||||||
|
c[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
|
||||||
|
c[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
|
||||||
|
c[ImGuiCol_ModalWindowDimBg] = ImVec4(0.16f, 0.16f, 0.16f, 0.73f);
|
||||||
|
|
||||||
|
ImGuiStyle &s = ImGui::GetStyle();
|
||||||
|
s.WindowRounding = 6.0;
|
||||||
|
s.FrameRounding = 6.0;
|
||||||
|
s.PopupRounding = 6.0;
|
||||||
|
g_base_style = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
void xemu_hud_init(SDL_Window* window, void* sdl_gl_context)
|
||||||
|
{
|
||||||
|
xemu_monitor_init();
|
||||||
|
g_vsync = g_config.display.window.vsync;
|
||||||
|
|
||||||
|
InitCustomRendering();
|
||||||
|
|
||||||
|
// Setup Dear ImGui context
|
||||||
|
IMGUI_CHECKVERSION();
|
||||||
|
ImGui::CreateContext();
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||||
|
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
|
||||||
|
io.IniFilename = NULL;
|
||||||
|
|
||||||
|
// Setup Platform/Renderer bindings
|
||||||
|
ImGui_ImplSDL2_InitForOpenGL(window, sdl_gl_context);
|
||||||
|
ImGui_ImplOpenGL3_Init("#version 150");
|
||||||
|
g_sdl_window = window;
|
||||||
|
ImPlot::CreateContext();
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
if (!g_config.general.show_welcome && g_config.general.updates.check) {
|
||||||
|
update_window.CheckForUpdates();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
g_last_scale = g_viewport_mgr.m_scale;
|
||||||
|
InitializeStyle();
|
||||||
|
g_main_menu.SetNextViewIndex(g_config.general.last_viewed_menu_index);
|
||||||
|
first_boot_window.is_open = g_config.general.show_welcome;
|
||||||
|
}
|
||||||
|
|
||||||
|
void xemu_hud_cleanup(void)
|
||||||
|
{
|
||||||
|
ImGui_ImplOpenGL3_Shutdown();
|
||||||
|
ImGui_ImplSDL2_Shutdown();
|
||||||
|
ImGui::DestroyContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
void xemu_hud_process_sdl_events(SDL_Event *event)
|
||||||
|
{
|
||||||
|
ImGui_ImplSDL2_ProcessEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void xemu_hud_should_capture_kbd_mouse(int *kbd, int *mouse)
|
||||||
|
{
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
if (kbd) *kbd = io.WantCaptureKeyboard;
|
||||||
|
if (mouse) *mouse = io.WantCaptureMouse;
|
||||||
|
}
|
||||||
|
|
||||||
|
void xemu_hud_set_framebuffer_texture(GLuint tex, bool flip)
|
||||||
|
{
|
||||||
|
g_tex = tex;
|
||||||
|
g_flip_req = flip;
|
||||||
|
}
|
||||||
|
|
||||||
|
void xemu_hud_render(void)
|
||||||
|
{
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
uint32_t now = SDL_GetTicks();
|
||||||
|
|
||||||
|
g_viewport_mgr.Update();
|
||||||
|
g_font_mgr.Update();
|
||||||
|
if (g_last_scale != g_viewport_mgr.m_scale) {
|
||||||
|
ImGuiStyle &style = ImGui::GetStyle();
|
||||||
|
style = g_base_style;
|
||||||
|
style.ScaleAllSizes(g_viewport_mgr.m_scale);
|
||||||
|
g_last_scale = g_viewport_mgr.m_scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!first_boot_window.is_open) {
|
||||||
|
RenderFramebuffer(g_tex, io.DisplaySize.x, io.DisplaySize.y, g_flip_req);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui_ImplOpenGL3_NewFrame();
|
||||||
|
io.ConfigFlags &= ~ImGuiConfigFlags_NavEnableGamepad;
|
||||||
|
ImGui_ImplSDL2_NewFrame(g_sdl_window);
|
||||||
|
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
|
||||||
|
io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
|
||||||
|
g_input_mgr.Update();
|
||||||
|
|
||||||
|
ImGui::NewFrame();
|
||||||
|
ProcessKeyboardShortcuts();
|
||||||
|
|
||||||
|
#if defined(DEBUG_NV2A_GL) && defined(CONFIG_RENDERDOC)
|
||||||
|
if (capture_renderdoc_frame) {
|
||||||
|
nv2a_dbg_renderdoc_capture_frames(1);
|
||||||
|
capture_renderdoc_frame = false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (g_config.display.ui.show_menubar && !first_boot_window.is_open) {
|
||||||
|
// Auto-hide main menu after 5s of inactivity
|
||||||
|
static uint32_t last_check = 0;
|
||||||
|
float alpha = 1.0;
|
||||||
|
const uint32_t timeout = 5000;
|
||||||
|
const float fade_duration = 1000.0;
|
||||||
|
bool menu_wakeup = g_input_mgr.MouseMoved();
|
||||||
|
if (menu_wakeup) {
|
||||||
|
last_check = now;
|
||||||
|
}
|
||||||
|
if ((now-last_check) > timeout) {
|
||||||
|
if (g_config.display.ui.use_animations) {
|
||||||
|
float t = fmin((float)((now-last_check)-timeout)/fade_duration, 1);
|
||||||
|
alpha = 1-t;
|
||||||
|
if (t >= 1) {
|
||||||
|
alpha = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alpha = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (alpha > 0.0) {
|
||||||
|
ImVec4 tc = ImGui::GetStyle().Colors[ImGuiCol_Text];
|
||||||
|
tc.w = alpha;
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, tc);
|
||||||
|
ImGui::SetNextWindowBgAlpha(alpha);
|
||||||
|
ShowMainMenu();
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
} else {
|
||||||
|
g_main_menu_height = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow) &&
|
||||||
|
!g_scene_mgr.IsDisplayingScene()) {
|
||||||
|
|
||||||
|
// If the guide button is pressed, wake the ui
|
||||||
|
bool menu_button = false;
|
||||||
|
uint32_t buttons = g_input_mgr.CombinedButtons();
|
||||||
|
if (buttons & CONTROLLER_BUTTON_GUIDE) {
|
||||||
|
menu_button = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow controllers without a guide button to also work
|
||||||
|
if ((buttons & CONTROLLER_BUTTON_BACK) &&
|
||||||
|
(buttons & CONTROLLER_BUTTON_START)) {
|
||||||
|
menu_button = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_F1)) {
|
||||||
|
g_scene_mgr.PushScene(g_main_menu);
|
||||||
|
} else if (ImGui::IsKeyPressed(ImGuiKey_F2)) {
|
||||||
|
g_scene_mgr.PushScene(g_popup_menu);
|
||||||
|
} else if (menu_button ||
|
||||||
|
(ImGui::IsMouseClicked(ImGuiMouseButton_Right) &&
|
||||||
|
!ImGui::IsAnyItemFocused() && !ImGui::IsAnyItemHovered())) {
|
||||||
|
g_scene_mgr.PushScene(g_popup_menu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
first_boot_window.Draw();
|
||||||
|
monitor_window.Draw();
|
||||||
|
apu_window.Draw();
|
||||||
|
video_window.Draw();
|
||||||
|
compatibility_reporter_window.Draw();
|
||||||
|
#if defined(_WIN32)
|
||||||
|
update_window.Draw();
|
||||||
|
#endif
|
||||||
|
g_scene_mgr.Draw();
|
||||||
|
if (!first_boot_window.is_open) notification_manager.Draw();
|
||||||
|
|
||||||
|
// static bool show_demo = true;
|
||||||
|
// if (show_demo) ImGui::ShowDemoWindow(&show_demo);
|
||||||
|
|
||||||
|
ImGui::Render();
|
||||||
|
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||||
|
|
||||||
|
if (g_vsync != g_config.display.window.vsync) {
|
||||||
|
g_vsync = g_config.display.window.vsync;
|
||||||
|
SDL_GL_SetSwapInterval(g_vsync ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_screenshot_pending) {
|
||||||
|
SaveScreenshot(g_tex, g_flip_req);
|
||||||
|
g_screenshot_pending = false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,192 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#include "common.hh"
|
||||||
|
#include "main-menu.hh"
|
||||||
|
#include "menubar.hh"
|
||||||
|
#include "misc.hh"
|
||||||
|
#include "widgets.hh"
|
||||||
|
#include "monitor.hh"
|
||||||
|
#include "debug.hh"
|
||||||
|
#include "actions.hh"
|
||||||
|
#include "compat.hh"
|
||||||
|
#include "update.hh"
|
||||||
|
#include "../xemu-os-utils.h"
|
||||||
|
|
||||||
|
extern float g_main_menu_height; // FIXME
|
||||||
|
|
||||||
|
#ifdef CONFIG_RENDERDOC
|
||||||
|
static bool capture_renderdoc_frame = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
#define SHORTCUT_MENU_TEXT(c) "Cmd+" #c
|
||||||
|
#else
|
||||||
|
#define SHORTCUT_MENU_TEXT(c) "Ctrl+" #c
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void ProcessKeyboardShortcuts(void)
|
||||||
|
{
|
||||||
|
if (IsShortcutKeyPressed(ImGuiKey_E)) {
|
||||||
|
ActionEjectDisc();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsShortcutKeyPressed(ImGuiKey_O)) {
|
||||||
|
ActionLoadDisc();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsShortcutKeyPressed(ImGuiKey_P)) {
|
||||||
|
ActionTogglePause();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsShortcutKeyPressed(ImGuiKey_R)) {
|
||||||
|
ActionReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsShortcutKeyPressed(ImGuiKey_Q)) {
|
||||||
|
ActionShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_GraveAccent)) {
|
||||||
|
monitor_window.ToggleOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(DEBUG_NV2A_GL) && defined(CONFIG_RENDERDOC)
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_F10)) {
|
||||||
|
nv2a_dbg_renderdoc_capture_frames(1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowMainMenu()
|
||||||
|
{
|
||||||
|
bool running = runstate_is_running();
|
||||||
|
|
||||||
|
if (ImGui::BeginMainMenuBar())
|
||||||
|
{
|
||||||
|
if (ImGui::BeginMenu("Machine"))
|
||||||
|
{
|
||||||
|
if (ImGui::MenuItem(running ? "Pause" : "Resume", SHORTCUT_MENU_TEXT(P))) ActionTogglePause();
|
||||||
|
if (ImGui::MenuItem("Screenshot")) ActionScreenshot();
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
if (ImGui::MenuItem("Eject Disc", SHORTCUT_MENU_TEXT(E))) ActionEjectDisc();
|
||||||
|
if (ImGui::MenuItem("Load Disc...", SHORTCUT_MENU_TEXT(O))) ActionLoadDisc();
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
ImGui::MenuItem("Settings", NULL, false, false);
|
||||||
|
if (ImGui::MenuItem(" General")) g_main_menu.ShowGeneral();
|
||||||
|
if (ImGui::MenuItem(" Input")) g_main_menu.ShowInput();
|
||||||
|
if (ImGui::MenuItem(" Display")) g_main_menu.ShowDisplay();
|
||||||
|
if (ImGui::MenuItem(" Audio")) g_main_menu.ShowAudio();
|
||||||
|
if (ImGui::MenuItem(" Network")) g_main_menu.ShowNetwork();
|
||||||
|
if (ImGui::MenuItem(" System")) g_main_menu.ShowSystem();
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
if (ImGui::MenuItem("Reset", SHORTCUT_MENU_TEXT(R))) ActionReset();
|
||||||
|
if (ImGui::MenuItem("Exit", SHORTCUT_MENU_TEXT(Q))) ActionShutdown();
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginMenu("View"))
|
||||||
|
{
|
||||||
|
int ui_scale_idx;
|
||||||
|
if (g_config.display.ui.auto_scale) {
|
||||||
|
ui_scale_idx = 0;
|
||||||
|
} else {
|
||||||
|
ui_scale_idx = g_config.display.ui.scale;
|
||||||
|
if (ui_scale_idx < 0) ui_scale_idx = 0;
|
||||||
|
else if (ui_scale_idx > 2) ui_scale_idx = 2;
|
||||||
|
}
|
||||||
|
if (ImGui::Combo("UI Scale", &ui_scale_idx,
|
||||||
|
"Auto\0"
|
||||||
|
"1x\0"
|
||||||
|
"2x\0")) {
|
||||||
|
if (ui_scale_idx == 0) {
|
||||||
|
g_config.display.ui.auto_scale = true;
|
||||||
|
} else {
|
||||||
|
g_config.display.ui.auto_scale = false;
|
||||||
|
g_config.display.ui.scale = ui_scale_idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int rendering_scale = nv2a_get_surface_scale_factor() - 1;
|
||||||
|
if (ImGui::Combo("Int. Resolution Scale", &rendering_scale,
|
||||||
|
"1x\0"
|
||||||
|
"2x\0"
|
||||||
|
"3x\0"
|
||||||
|
"4x\0"
|
||||||
|
"5x\0"
|
||||||
|
"6x\0"
|
||||||
|
"7x\0"
|
||||||
|
"8x\0"
|
||||||
|
"9x\0"
|
||||||
|
"10x\0")) {
|
||||||
|
nv2a_set_surface_scale_factor(rendering_scale + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Combo("Display Mode", &g_config.display.ui.fit,
|
||||||
|
"Center\0Scale\0Scale (Widescreen 16:9)\0Scale "
|
||||||
|
"(4:3)\0Stretch\0");
|
||||||
|
ImGui::SameLine();
|
||||||
|
HelpMarker("Controls how the rendered content should be scaled "
|
||||||
|
"into the window");
|
||||||
|
if (ImGui::MenuItem("Fullscreen", SHORTCUT_MENU_TEXT(Alt + F),
|
||||||
|
xemu_is_fullscreen(), true)) {
|
||||||
|
xemu_toggle_fullscreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginMenu("Debug"))
|
||||||
|
{
|
||||||
|
ImGui::MenuItem("Monitor", "~", &monitor_window.is_open);
|
||||||
|
ImGui::MenuItem("Audio", NULL, &apu_window.m_is_open);
|
||||||
|
ImGui::MenuItem("Video", NULL, &video_window.m_is_open);
|
||||||
|
#if defined(DEBUG_NV2A_GL) && defined(CONFIG_RENDERDOC)
|
||||||
|
if (nv2a_dbg_renderdoc_available()) {
|
||||||
|
ImGui::MenuItem("RenderDoc: Capture", NULL, &capture_renderdoc_frame);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginMenu("Help"))
|
||||||
|
{
|
||||||
|
if (ImGui::MenuItem("Help", NULL)) {
|
||||||
|
xemu_open_web_browser("https://xemu.app/docs/getting-started/");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::MenuItem("Report Compatibility...", NULL,
|
||||||
|
&compatibility_reporter_window.is_open);
|
||||||
|
#if defined(_WIN32)
|
||||||
|
ImGui::MenuItem("Check for Updates...", NULL, &update_window.is_open);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
if (ImGui::MenuItem("About")) g_main_menu.ShowAbout();
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
g_main_menu_height = ImGui::GetWindowHeight();
|
||||||
|
ImGui::EndMainMenuBar();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
void ProcessKeyboardShortcuts(void);
|
||||||
|
void ShowMainMenu();
|
|
@ -0,0 +1,24 @@
|
||||||
|
xemu_ss.add(files(
|
||||||
|
'actions.cc',
|
||||||
|
'animation.cc',
|
||||||
|
'compat.cc',
|
||||||
|
'debug.cc',
|
||||||
|
'font-manager.cc',
|
||||||
|
'gl-helpers.cc',
|
||||||
|
'input-manager.cc',
|
||||||
|
'main-menu.cc',
|
||||||
|
'main.cc',
|
||||||
|
'menubar.cc',
|
||||||
|
'monitor.cc',
|
||||||
|
'notifications.cc',
|
||||||
|
'popup-menu.cc',
|
||||||
|
'reporting.cc',
|
||||||
|
'scene-components.cc',
|
||||||
|
'scene-manager.cc',
|
||||||
|
'scene.cc',
|
||||||
|
'viewport-manager.cc',
|
||||||
|
'welcome.cc',
|
||||||
|
'widgets.cc',
|
||||||
|
))
|
||||||
|
|
||||||
|
xemu_ss.add(when: 'CONFIG_WIN32', if_true: files('update.cc'))
|
|
@ -0,0 +1,106 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <cstdio>
|
||||||
|
#include "common.hh"
|
||||||
|
#include "xemu-hud.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <noc_file_dialog.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
bool IsNavInputPressed(ImGuiNavInput i) {
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
return io.NavInputs[i] > 0.0f && io.NavInputsDownDuration[i] == 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static inline const char *PausedFileOpen(int flags, const char *filters,
|
||||||
|
const char *default_path,
|
||||||
|
const char *default_name)
|
||||||
|
{
|
||||||
|
bool is_running = runstate_is_running();
|
||||||
|
if (is_running) {
|
||||||
|
vm_stop(RUN_STATE_PAUSED);
|
||||||
|
}
|
||||||
|
const char *r = noc_file_dialog_open(flags, filters, default_path, default_name);
|
||||||
|
if (is_running) {
|
||||||
|
vm_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ... Args>
|
||||||
|
std::string string_format( const std::string& format, Args ... args )
|
||||||
|
{
|
||||||
|
int size_s = std::snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0'
|
||||||
|
if( size_s <= 0 ){ throw std::runtime_error( "Error during formatting." ); }
|
||||||
|
auto size = static_cast<size_t>( size_s );
|
||||||
|
std::unique_ptr<char[]> buf( new char[ size ] );
|
||||||
|
std::snprintf( buf.get(), size, format.c_str(), args ... );
|
||||||
|
return std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool IsShortcutKeyPressed(int scancode)
|
||||||
|
{
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
const bool is_osx = io.ConfigMacOSXBehaviors;
|
||||||
|
const bool is_shortcut_key = (is_osx ? (io.KeySuper && !io.KeyCtrl) : (io.KeyCtrl && !io.KeySuper)) && !io.KeyAlt && !io.KeyShift; // OS X style: Shortcuts using Cmd/Super instead of Ctrl
|
||||||
|
return is_shortcut_key && ImGui::IsKeyPressed(scancode);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline float mix(float a, float b, float t)
|
||||||
|
{
|
||||||
|
return a*(1.0-t) + (b-a)*t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
int PushWindowTransparencySettings(bool transparent, float alpha_transparent = 0.4, float alpha_opaque = 1.0)
|
||||||
|
{
|
||||||
|
float alpha = transparent ? alpha_transparent : alpha_opaque;
|
||||||
|
|
||||||
|
ImVec4 c;
|
||||||
|
|
||||||
|
c = ImGui::GetStyle().Colors[transparent ? ImGuiCol_WindowBg : ImGuiCol_TitleBg];
|
||||||
|
c.w *= alpha;
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_TitleBg, c);
|
||||||
|
|
||||||
|
c = ImGui::GetStyle().Colors[transparent ? ImGuiCol_WindowBg : ImGuiCol_TitleBgActive];
|
||||||
|
c.w *= alpha;
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, c);
|
||||||
|
|
||||||
|
c = ImGui::GetStyle().Colors[ImGuiCol_WindowBg];
|
||||||
|
c.w *= alpha;
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, c);
|
||||||
|
|
||||||
|
c = ImGui::GetStyle().Colors[ImGuiCol_Border];
|
||||||
|
c.w *= alpha;
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Border, c);
|
||||||
|
|
||||||
|
c = ImGui::GetStyle().Colors[ImGuiCol_FrameBg];
|
||||||
|
c.w *= alpha;
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_FrameBg, c);
|
||||||
|
|
||||||
|
return 5;
|
||||||
|
}
|
|
@ -0,0 +1,155 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#include "monitor.hh"
|
||||||
|
#include "imgui.h"
|
||||||
|
#include "misc.hh"
|
||||||
|
#include "font-manager.hh"
|
||||||
|
|
||||||
|
// Portable helpers
|
||||||
|
static int Stricmp(const char* str1, const char* str2) { int d; while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; } return d; }
|
||||||
|
static char* Strdup(const char *str) { size_t len = strlen(str) + 1; void* buf = malloc(len); IM_ASSERT(buf); return (char*)memcpy(buf, (const void*)str, len); }
|
||||||
|
static void Strtrim(char* str) { char* str_end = str + strlen(str); while (str_end > str && str_end[-1] == ' ') str_end--; *str_end = 0; }
|
||||||
|
|
||||||
|
MonitorWindow::MonitorWindow()
|
||||||
|
{
|
||||||
|
is_open = false;
|
||||||
|
memset(InputBuf, 0, sizeof(InputBuf));
|
||||||
|
HistoryPos = -1;
|
||||||
|
AutoScroll = true;
|
||||||
|
ScrollToBottom = false;
|
||||||
|
}
|
||||||
|
MonitorWindow::~MonitorWindow()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void MonitorWindow::Draw()
|
||||||
|
{
|
||||||
|
if (!is_open) return;
|
||||||
|
int style_pop_cnt = PushWindowTransparencySettings(true) + 1;
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImU32(ImColor(0, 0, 0, 80)));
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
ImVec2 window_pos = ImVec2(0,io.DisplaySize.y/2);
|
||||||
|
ImGui::SetNextWindowPos(window_pos, ImGuiCond_Appearing);
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(io.DisplaySize.x, io.DisplaySize.y/2), ImGuiCond_Appearing);
|
||||||
|
if (ImGui::Begin("Monitor", &is_open, ImGuiWindowFlags_NoCollapse)) {
|
||||||
|
const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); // 1 separator, 1 input text
|
||||||
|
ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), false, ImGuiWindowFlags_HorizontalScrollbar); // Leave room for 1 separator + 1 InputText
|
||||||
|
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4,1)); // Tighten spacing
|
||||||
|
ImGui::PushFont(g_font_mgr.m_fixed_width_font);
|
||||||
|
ImGui::TextUnformatted(xemu_get_monitor_buffer());
|
||||||
|
ImGui::PopFont();
|
||||||
|
|
||||||
|
if (ScrollToBottom || (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())) {
|
||||||
|
ImGui::SetScrollHereY(1.0f);
|
||||||
|
}
|
||||||
|
ScrollToBottom = false;
|
||||||
|
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
ImGui::EndChild();
|
||||||
|
ImGui::Separator();
|
||||||
|
// Command-line
|
||||||
|
bool reclaim_focus = ImGui::IsWindowAppearing();
|
||||||
|
|
||||||
|
ImGui::SetNextItemWidth(-1);
|
||||||
|
ImGui::PushFont(g_font_mgr.m_fixed_width_font);
|
||||||
|
if (ImGui::InputText("#commandline", InputBuf, IM_ARRAYSIZE(InputBuf), ImGuiInputTextFlags_EnterReturnsTrue|ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_CallbackHistory, &TextEditCallbackStub, (void*)this)) {
|
||||||
|
char* s = InputBuf;
|
||||||
|
Strtrim(s);
|
||||||
|
if (s[0])
|
||||||
|
ExecCommand(s);
|
||||||
|
strcpy(s, "");
|
||||||
|
reclaim_focus = true;
|
||||||
|
}
|
||||||
|
ImGui::PopFont();
|
||||||
|
// Auto-focus on window apparition
|
||||||
|
ImGui::SetItemDefaultFocus();
|
||||||
|
if (reclaim_focus) {
|
||||||
|
ImGui::SetKeyboardFocusHere(-1); // Auto focus previous widget
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
ImGui::PopStyleColor(style_pop_cnt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MonitorWindow::ToggleOpen(void)
|
||||||
|
{
|
||||||
|
is_open = !is_open;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MonitorWindow::ExecCommand(const char* command_line)
|
||||||
|
{
|
||||||
|
xemu_run_monitor_command(command_line);
|
||||||
|
|
||||||
|
// Insert into history. First find match and delete it so it can be pushed to the back. This isn't trying to be smart or optimal.
|
||||||
|
HistoryPos = -1;
|
||||||
|
for (int i = History.Size-1; i >= 0; i--)
|
||||||
|
if (Stricmp(History[i], command_line) == 0)
|
||||||
|
{
|
||||||
|
free(History[i]);
|
||||||
|
History.erase(History.begin() + i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
History.push_back(Strdup(command_line));
|
||||||
|
|
||||||
|
// On commad input, we scroll to bottom even if AutoScroll==false
|
||||||
|
ScrollToBottom = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MonitorWindow::TextEditCallbackStub(ImGuiInputTextCallbackData* data) // In C++11 you are better off using lambdas for this sort of forwarding callbacks
|
||||||
|
{
|
||||||
|
MonitorWindow* console = (MonitorWindow*)data->UserData;
|
||||||
|
return console->TextEditCallback(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
int MonitorWindow::TextEditCallback(ImGuiInputTextCallbackData* data)
|
||||||
|
{
|
||||||
|
switch (data->EventFlag)
|
||||||
|
{
|
||||||
|
case ImGuiInputTextFlags_CallbackHistory:
|
||||||
|
{
|
||||||
|
// Example of HISTORY
|
||||||
|
const int prev_history_pos = HistoryPos;
|
||||||
|
if (data->EventKey == ImGuiKey_UpArrow)
|
||||||
|
{
|
||||||
|
if (HistoryPos == -1)
|
||||||
|
HistoryPos = History.Size - 1;
|
||||||
|
else if (HistoryPos > 0)
|
||||||
|
HistoryPos--;
|
||||||
|
}
|
||||||
|
else if (data->EventKey == ImGuiKey_DownArrow)
|
||||||
|
{
|
||||||
|
if (HistoryPos != -1)
|
||||||
|
if (++HistoryPos >= History.Size)
|
||||||
|
HistoryPos = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A better implementation would preserve the data on the current input line along with cursor position.
|
||||||
|
if (prev_history_pos != HistoryPos)
|
||||||
|
{
|
||||||
|
const char* history_str = (HistoryPos >= 0) ? History[HistoryPos] : "";
|
||||||
|
data->DeleteChars(0, data->BufTextLen);
|
||||||
|
data->InsertChars(0, history_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
MonitorWindow monitor_window;
|
|
@ -0,0 +1,50 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
#include "../xemu-monitor.h"
|
||||||
|
#include "common.hh"
|
||||||
|
|
||||||
|
class MonitorWindow
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool is_open;
|
||||||
|
|
||||||
|
private:
|
||||||
|
char InputBuf[256];
|
||||||
|
ImVector<char*> Items;
|
||||||
|
ImVector<const char*> Commands;
|
||||||
|
ImVector<char*> History;
|
||||||
|
int HistoryPos; // -1: new line, 0..History.Size-1 browsing history.
|
||||||
|
ImGuiTextFilter Filter;
|
||||||
|
bool AutoScroll;
|
||||||
|
bool ScrollToBottom;
|
||||||
|
|
||||||
|
public:
|
||||||
|
MonitorWindow();
|
||||||
|
~MonitorWindow();
|
||||||
|
void Draw();
|
||||||
|
void ToggleOpen(void);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ExecCommand(const char* command_line);
|
||||||
|
static int TextEditCallbackStub(ImGuiInputTextCallbackData* data);
|
||||||
|
int TextEditCallback(ImGuiInputTextCallbackData* data);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern MonitorWindow monitor_window;
|
|
@ -0,0 +1,155 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#include "notifications.hh"
|
||||||
|
#include "common.hh"
|
||||||
|
|
||||||
|
#include "../xemu-notifications.h"
|
||||||
|
|
||||||
|
NotificationManager notification_manager;
|
||||||
|
|
||||||
|
NotificationManager::NotificationManager()
|
||||||
|
{
|
||||||
|
m_active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationManager::QueueNotification(const char *msg)
|
||||||
|
{
|
||||||
|
m_notification_queue.push_back(strdup(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationManager::QueueError(const char *msg)
|
||||||
|
{
|
||||||
|
m_error_queue.push_back(strdup(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationManager::Draw()
|
||||||
|
{
|
||||||
|
uint32_t now = SDL_GetTicks();
|
||||||
|
|
||||||
|
if (m_active) {
|
||||||
|
// Currently displaying a notification
|
||||||
|
float t =
|
||||||
|
(m_notification_end_time - now) / (float)kNotificationDuration;
|
||||||
|
if (t > 1.0) {
|
||||||
|
// Notification delivered, free it
|
||||||
|
free((void *)m_msg);
|
||||||
|
m_active = false;
|
||||||
|
} else {
|
||||||
|
// Notification should be displayed
|
||||||
|
DrawNotification(t, m_msg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Check to see if a notification is pending
|
||||||
|
if (m_notification_queue.size() > 0) {
|
||||||
|
m_msg = m_notification_queue[0];
|
||||||
|
m_active = true;
|
||||||
|
m_notification_end_time = now + kNotificationDuration;
|
||||||
|
m_notification_queue.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
|
||||||
|
if (m_error_queue.size() > 0) {
|
||||||
|
ImGui::OpenPopup("Error");
|
||||||
|
ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x/2, io.DisplaySize.y/2),
|
||||||
|
ImGuiCond_Always, ImVec2(0.5, 0.5));
|
||||||
|
}
|
||||||
|
if (ImGui::BeginPopupModal("Error", NULL, ImGuiWindowFlags_AlwaysAutoResize))
|
||||||
|
{
|
||||||
|
ImGui::Text("%s", m_error_queue[0]);
|
||||||
|
ImGui::Dummy(ImVec2(0,16));
|
||||||
|
ImGui::SetItemDefaultFocus();
|
||||||
|
ImGuiStyle &style = ImGui::GetStyle();
|
||||||
|
ImGui::SetCursorPosX(ImGui::GetWindowWidth()-(120+2*style.FramePadding.x));
|
||||||
|
if (ImGui::Button("Ok", ImVec2(120, 0))) {
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
free((void*)m_error_queue[0]);
|
||||||
|
m_error_queue.pop_front();
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationManager::DrawNotification(float t, const char *msg)
|
||||||
|
{
|
||||||
|
const float DISTANCE = 10.0f;
|
||||||
|
static int corner = 1;
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
if (corner != -1)
|
||||||
|
{
|
||||||
|
ImVec2 window_pos = ImVec2((corner & 1) ? io.DisplaySize.x - DISTANCE : DISTANCE, (corner & 2) ? io.DisplaySize.y - DISTANCE : DISTANCE);
|
||||||
|
window_pos.y = g_main_menu_height + DISTANCE;
|
||||||
|
ImVec2 window_pos_pivot = ImVec2((corner & 1) ? 1.0f : 0.0f, (corner & 2) ? 1.0f : 0.0f);
|
||||||
|
ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot);
|
||||||
|
}
|
||||||
|
|
||||||
|
const float fade_in = 0.1;
|
||||||
|
const float fade_out = 0.9;
|
||||||
|
float fade = 0;
|
||||||
|
|
||||||
|
if (t < fade_in) {
|
||||||
|
// Linear fade in
|
||||||
|
fade = t/fade_in;
|
||||||
|
} else if (t >= fade_out) {
|
||||||
|
// Linear fade out
|
||||||
|
fade = 1-(t-fade_out)/(1-fade_out);
|
||||||
|
} else {
|
||||||
|
// Constant
|
||||||
|
fade = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec4 color = ImGui::GetStyle().Colors[ImGuiCol_ButtonActive];
|
||||||
|
color.w *= fade;
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 1);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0,0,0,fade*0.9f));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Border, color);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||||
|
ImGui::SetNextWindowBgAlpha(0.90f * fade);
|
||||||
|
if (ImGui::Begin("Notification", NULL,
|
||||||
|
ImGuiWindowFlags_Tooltip |
|
||||||
|
ImGuiWindowFlags_NoMove |
|
||||||
|
ImGuiWindowFlags_NoDecoration |
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize |
|
||||||
|
ImGuiWindowFlags_NoSavedSettings |
|
||||||
|
ImGuiWindowFlags_NoFocusOnAppearing |
|
||||||
|
ImGuiWindowFlags_NoNav |
|
||||||
|
ImGuiWindowFlags_NoInputs
|
||||||
|
))
|
||||||
|
{
|
||||||
|
ImGui::Text("%s", msg);
|
||||||
|
}
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* External interface, exposed via xemu-notifications.h */
|
||||||
|
|
||||||
|
void xemu_queue_notification(const char *msg)
|
||||||
|
{
|
||||||
|
notification_manager.QueueNotification(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void xemu_queue_error_message(const char *msg)
|
||||||
|
{
|
||||||
|
notification_manager.QueueError(msg);
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <deque>
|
||||||
|
|
||||||
|
#include "../xemu-notifications.h"
|
||||||
|
|
||||||
|
class NotificationManager
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::deque<const char *> m_notification_queue;
|
||||||
|
std::deque<const char *> m_error_queue;
|
||||||
|
|
||||||
|
const int kNotificationDuration = 4000;
|
||||||
|
uint32_t m_notification_end_time;
|
||||||
|
const char *m_msg;
|
||||||
|
bool m_active;
|
||||||
|
|
||||||
|
public:
|
||||||
|
NotificationManager();
|
||||||
|
void QueueNotification(const char *msg);
|
||||||
|
void QueueError(const char *msg);
|
||||||
|
void Draw();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void DrawNotification(float t, const char *msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern NotificationManager notification_manager;
|
|
@ -0,0 +1,511 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include "misc.hh"
|
||||||
|
#include "actions.hh"
|
||||||
|
#include "font-manager.hh"
|
||||||
|
#include "viewport-manager.hh"
|
||||||
|
#include "scene-manager.hh"
|
||||||
|
#include "popup-menu.hh"
|
||||||
|
#include "input-manager.hh"
|
||||||
|
#include "xemu-hud.h"
|
||||||
|
#include "IconsFontAwesome6.h"
|
||||||
|
|
||||||
|
PopupMenuItemDelegate::~PopupMenuItemDelegate() {}
|
||||||
|
void PopupMenuItemDelegate::PushMenu(PopupMenu &menu) {}
|
||||||
|
void PopupMenuItemDelegate::PopMenu() {}
|
||||||
|
void PopupMenuItemDelegate::ClearMenuStack() {}
|
||||||
|
void PopupMenuItemDelegate::LostFocus() {}
|
||||||
|
void PopupMenuItemDelegate::PushFocus() {}
|
||||||
|
void PopupMenuItemDelegate::PopFocus() {}
|
||||||
|
bool PopupMenuItemDelegate::DidPop() { return false; }
|
||||||
|
|
||||||
|
bool PopupMenuButton(std::string text, std::string icon = "")
|
||||||
|
{
|
||||||
|
ImGui::PushFont(g_font_mgr.m_menu_font);
|
||||||
|
auto button_text = string_format("%s %s", icon.c_str(), text.c_str());
|
||||||
|
bool status = ImGui::Button(button_text.c_str(), ImVec2(-FLT_MIN, 0));
|
||||||
|
ImGui::PopFont();
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PopupMenuCheck(std::string text, std::string icon = "", bool v = false)
|
||||||
|
{
|
||||||
|
bool status = PopupMenuButton(text, icon);
|
||||||
|
if (v) {
|
||||||
|
ImGui::PushFont(g_font_mgr.m_menu_font);
|
||||||
|
const ImVec2 p0 = ImGui::GetItemRectMin();
|
||||||
|
const ImVec2 p1 = ImGui::GetItemRectMax();
|
||||||
|
const char *icon = ICON_FA_CHECK;
|
||||||
|
ImVec2 ts_icon = ImGui::CalcTextSize(icon);
|
||||||
|
ImDrawList *draw_list = ImGui::GetWindowDrawList();
|
||||||
|
ImGuiStyle &style = ImGui::GetStyle();
|
||||||
|
draw_list->AddText(ImVec2(p1.x - style.FramePadding.x - ts_icon.x,
|
||||||
|
p0.y + (p1.y - p0.y - ts_icon.y) / 2),
|
||||||
|
ImGui::GetColorU32(ImGuiCol_Text), icon);
|
||||||
|
ImGui::PopFont();
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PopupMenuSubmenuButton(std::string text, std::string icon = "")
|
||||||
|
{
|
||||||
|
bool status = PopupMenuButton(text, icon);
|
||||||
|
|
||||||
|
ImGui::PushFont(g_font_mgr.m_menu_font);
|
||||||
|
const ImVec2 p0 = ImGui::GetItemRectMin();
|
||||||
|
const ImVec2 p1 = ImGui::GetItemRectMax();
|
||||||
|
const char *right_icon = ICON_FA_CHEVRON_RIGHT;
|
||||||
|
ImVec2 ts_icon = ImGui::CalcTextSize(right_icon);
|
||||||
|
ImDrawList *draw_list = ImGui::GetWindowDrawList();
|
||||||
|
ImGuiStyle &style = ImGui::GetStyle();
|
||||||
|
draw_list->AddText(ImVec2(p1.x - style.FramePadding.x - ts_icon.x,
|
||||||
|
p0.y + (p1.y - p0.y - ts_icon.y) / 2),
|
||||||
|
ImGui::GetColorU32(ImGuiCol_Text), right_icon);
|
||||||
|
ImGui::PopFont();
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PopupMenuToggle(std::string text, std::string icon = "", bool *v = nullptr)
|
||||||
|
{
|
||||||
|
bool l_v = false;
|
||||||
|
if (v == NULL) v = &l_v;
|
||||||
|
|
||||||
|
ImGuiStyle &style = ImGui::GetStyle();
|
||||||
|
bool status = PopupMenuButton(text, icon);
|
||||||
|
ImVec2 p_min = ImGui::GetItemRectMin();
|
||||||
|
ImVec2 p_max = ImGui::GetItemRectMax();
|
||||||
|
if (status) *v = !*v;
|
||||||
|
|
||||||
|
ImGui::PushFont(g_font_mgr.m_menu_font);
|
||||||
|
float title_height = ImGui::GetTextLineHeight();
|
||||||
|
ImGui::PopFont();
|
||||||
|
|
||||||
|
float toggle_height = title_height * 0.75;
|
||||||
|
ImVec2 toggle_size(toggle_height * 1.75, toggle_height);
|
||||||
|
ImVec2 toggle_pos(p_max.x - toggle_size.x - style.FramePadding.x,
|
||||||
|
p_min.y + (title_height - toggle_size.y)/2 + style.FramePadding.y);
|
||||||
|
DrawToggle(*v, ImGui::IsItemHovered(), toggle_pos, toggle_size);
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PopupMenuSlider(std::string text, std::string icon = "", float *v = NULL)
|
||||||
|
{
|
||||||
|
bool status = PopupMenuButton(text, icon);
|
||||||
|
ImVec2 p_min = ImGui::GetItemRectMin();
|
||||||
|
ImVec2 p_max = ImGui::GetItemRectMax();
|
||||||
|
|
||||||
|
ImGuiStyle &style = ImGui::GetStyle();
|
||||||
|
|
||||||
|
float new_v = *v;
|
||||||
|
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow) ||
|
||||||
|
ImGui::IsKeyPressed(ImGuiKey_GamepadDpadLeft) ||
|
||||||
|
ImGui::IsKeyPressed(ImGuiKey_GamepadLStickLeft) ||
|
||||||
|
ImGui::IsKeyPressed(ImGuiKey_GamepadRStickLeft)) new_v -= 0.05;
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_RightArrow) ||
|
||||||
|
ImGui::IsKeyPressed(ImGuiKey_GamepadDpadRight) ||
|
||||||
|
ImGui::IsKeyPressed(ImGuiKey_GamepadLStickRight) ||
|
||||||
|
ImGui::IsKeyPressed(ImGuiKey_GamepadRStickRight)) new_v += 0.05;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::PushFont(g_font_mgr.m_menu_font);
|
||||||
|
float title_height = ImGui::GetTextLineHeight();
|
||||||
|
ImGui::PopFont();
|
||||||
|
|
||||||
|
float toggle_height = title_height * 0.75;
|
||||||
|
ImVec2 slider_size(toggle_height * 3.75, toggle_height);
|
||||||
|
ImVec2 slider_pos(p_max.x - slider_size.x - style.FramePadding.x,
|
||||||
|
p_min.y + (title_height - slider_size.y)/2 + style.FramePadding.y);
|
||||||
|
|
||||||
|
if (ImGui::IsItemActive()) {
|
||||||
|
ImVec2 mouse = ImGui::GetMousePos();
|
||||||
|
new_v = GetSliderValueForMousePos(mouse, slider_pos, slider_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawSlider(*v, ImGui::IsItemActive() || ImGui::IsItemHovered(), slider_pos,
|
||||||
|
slider_size);
|
||||||
|
|
||||||
|
*v = fmin(fmax(0, new_v), 1.0);
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
PopupMenu::PopupMenu() : m_animation(0.12, 0.12), m_ease_direction(0, 0)
|
||||||
|
{
|
||||||
|
m_focus = false;
|
||||||
|
m_pop_focus = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopupMenu::InitFocus()
|
||||||
|
{
|
||||||
|
m_pop_focus = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
PopupMenu::~PopupMenu()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopupMenu::Show(const ImVec2 &direction)
|
||||||
|
{
|
||||||
|
m_animation.EaseIn();
|
||||||
|
m_ease_direction = direction;
|
||||||
|
m_focus = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopupMenu::Hide(const ImVec2 &direction)
|
||||||
|
{
|
||||||
|
m_animation.EaseOut();
|
||||||
|
m_ease_direction = direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PopupMenu::IsAnimating()
|
||||||
|
{
|
||||||
|
return m_animation.IsAnimating();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopupMenu::Draw(PopupMenuItemDelegate &nav)
|
||||||
|
{
|
||||||
|
m_animation.Step();
|
||||||
|
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
float t = m_animation.GetSinInterpolatedValue();
|
||||||
|
float window_alpha = t;
|
||||||
|
ImVec2 window_pos = ImVec2(io.DisplaySize.x / 2 + (1-t) * m_ease_direction.x,
|
||||||
|
io.DisplaySize.y / 2 + (1-t) * m_ease_direction.y);
|
||||||
|
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, window_alpha);
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
|
||||||
|
g_viewport_mgr.Scale(ImVec2(10, 5)));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0);
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0.5));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_WindowBg));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_NavHighlight, IM_COL32_BLACK_TRANS);
|
||||||
|
|
||||||
|
if (m_focus) ImGui::SetNextWindowFocus();
|
||||||
|
ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, ImVec2(0.5, 0.5));
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(400*g_viewport_mgr.m_scale, 0), ImGuiCond_Always);
|
||||||
|
ImGui::SetNextWindowBgAlpha(0);
|
||||||
|
|
||||||
|
ImGui::Begin("###PopupMenu", NULL, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings);
|
||||||
|
if (DrawItems(nav)) nav.PopMenu();
|
||||||
|
if (!ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow)) nav.LostFocus();
|
||||||
|
ImVec2 pos = ImGui::GetWindowPos();
|
||||||
|
ImVec2 sz = ImGui::GetWindowSize();
|
||||||
|
ImGui::End();
|
||||||
|
|
||||||
|
if (!g_input_mgr.IsNavigatingWithController()) {
|
||||||
|
ImGui::PushFont(g_font_mgr.m_menu_font);
|
||||||
|
pos.y -= ImGui::GetFrameHeight();
|
||||||
|
ImGui::SetNextWindowPos(pos);
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(sz.x, ImGui::GetFrameHeight()));
|
||||||
|
ImGui::SetNextWindowBgAlpha(0);
|
||||||
|
ImGui::Begin("###PopupMenuNav", NULL, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 200));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS);
|
||||||
|
if (ImGui::Button(ICON_FA_ARROW_LEFT)) {
|
||||||
|
nav.PopMenu();
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetCursorPosX(ImGui::GetContentRegionMax().x - ImGui::GetStyle().FramePadding.x * 2.0f - ImGui::GetTextLineHeight());
|
||||||
|
if (ImGui::Button(ICON_FA_XMARK)) {
|
||||||
|
nav.ClearMenuStack();
|
||||||
|
}
|
||||||
|
ImGui::PopStyleColor(2);
|
||||||
|
ImGui::End();
|
||||||
|
ImGui::PopFont();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::PopStyleColor(2);
|
||||||
|
ImGui::PopStyleVar(7);
|
||||||
|
m_pop_focus = false;
|
||||||
|
m_focus = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PopupMenu::DrawItems(PopupMenuItemDelegate &nav)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DisplayModePopupMenu : public virtual PopupMenu {
|
||||||
|
public:
|
||||||
|
bool DrawItems(PopupMenuItemDelegate &nav) override
|
||||||
|
{
|
||||||
|
const char *values[] = {
|
||||||
|
"Center", "Scale", "Scale (Widescreen 16:9)", "Scale (4:3)", "Stretch"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < CONFIG_DISPLAY_UI_FIT__COUNT; i++) {
|
||||||
|
bool selected = g_config.display.ui.fit == i;
|
||||||
|
if (m_focus && selected) ImGui::SetKeyboardFocusHere();
|
||||||
|
if (PopupMenuCheck(values[i], "", selected))
|
||||||
|
g_config.display.ui.fit = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
extern Scene g_main_menu;
|
||||||
|
|
||||||
|
class SettingsPopupMenu : public virtual PopupMenu {
|
||||||
|
protected:
|
||||||
|
DisplayModePopupMenu display_mode;
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool DrawItems(PopupMenuItemDelegate &nav) override
|
||||||
|
{
|
||||||
|
bool pop = false;
|
||||||
|
|
||||||
|
if (m_focus && !m_pop_focus) {
|
||||||
|
ImGui::SetKeyboardFocusHere();
|
||||||
|
}
|
||||||
|
PopupMenuSlider("Volume", ICON_FA_VOLUME_HIGH, &g_config.audio.volume_limit);
|
||||||
|
bool fs = xemu_is_fullscreen();
|
||||||
|
if (PopupMenuToggle("Fullscreen", ICON_FA_WINDOW_MAXIMIZE, &fs)) {
|
||||||
|
xemu_toggle_fullscreen();
|
||||||
|
}
|
||||||
|
if (PopupMenuSubmenuButton("Display Mode", ICON_FA_EXPAND)) {
|
||||||
|
nav.PushFocus();
|
||||||
|
nav.PushMenu(display_mode);
|
||||||
|
}
|
||||||
|
if (PopupMenuButton("All settings...", ICON_FA_SLIDERS)) {
|
||||||
|
nav.ClearMenuStack();
|
||||||
|
g_scene_mgr.PushScene(g_main_menu);
|
||||||
|
}
|
||||||
|
if (m_pop_focus) {
|
||||||
|
nav.PopFocus();
|
||||||
|
}
|
||||||
|
return pop;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class RootPopupMenu : public virtual PopupMenu {
|
||||||
|
protected:
|
||||||
|
SettingsPopupMenu settings;
|
||||||
|
bool refocus_first_item;
|
||||||
|
|
||||||
|
public:
|
||||||
|
RootPopupMenu() {
|
||||||
|
refocus_first_item = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DrawItems(PopupMenuItemDelegate &nav) override
|
||||||
|
{
|
||||||
|
bool pop = false;
|
||||||
|
|
||||||
|
if (refocus_first_item || (m_focus && !m_pop_focus)) {
|
||||||
|
ImGui::SetKeyboardFocusHere();
|
||||||
|
refocus_first_item = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool running = runstate_is_running();
|
||||||
|
if (running) {
|
||||||
|
if (PopupMenuButton("Pause", ICON_FA_CIRCLE_PAUSE)) {
|
||||||
|
ActionTogglePause();
|
||||||
|
refocus_first_item = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (PopupMenuButton("Resume", ICON_FA_CIRCLE_PLAY)) {
|
||||||
|
ActionTogglePause();
|
||||||
|
refocus_first_item = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (PopupMenuButton("Screenshot", ICON_FA_CAMERA)) {
|
||||||
|
ActionScreenshot();
|
||||||
|
pop = true;
|
||||||
|
}
|
||||||
|
if (PopupMenuButton("Eject Disc", ICON_FA_EJECT)) {
|
||||||
|
ActionEjectDisc();
|
||||||
|
pop = true;
|
||||||
|
}
|
||||||
|
if (PopupMenuButton("Load Disc...", ICON_FA_COMPACT_DISC)) {
|
||||||
|
ActionLoadDisc();
|
||||||
|
pop = true;
|
||||||
|
}
|
||||||
|
if (PopupMenuSubmenuButton("Settings", ICON_FA_GEARS)) {
|
||||||
|
nav.PushFocus();
|
||||||
|
nav.PushMenu(settings);
|
||||||
|
}
|
||||||
|
if (PopupMenuButton("Restart", ICON_FA_ARROWS_ROTATE)) {
|
||||||
|
ActionReset();
|
||||||
|
pop = true;
|
||||||
|
}
|
||||||
|
if (PopupMenuButton("Exit", ICON_FA_POWER_OFF)) {
|
||||||
|
ActionShutdown();
|
||||||
|
pop = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_pop_focus) {
|
||||||
|
nav.PopFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
return pop;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
RootPopupMenu root_menu;
|
||||||
|
|
||||||
|
void PopupMenuScene::PushMenu(PopupMenu &menu)
|
||||||
|
{
|
||||||
|
menu.Show(m_view_stack.size() ? EASE_VECTOR_LEFT : EASE_VECTOR_DOWN);
|
||||||
|
m_menus_in_transition.push_back(&menu);
|
||||||
|
|
||||||
|
if (m_view_stack.size()) {
|
||||||
|
auto current = m_view_stack.back();
|
||||||
|
m_menus_in_transition.push_back(current);
|
||||||
|
current->Hide(EASE_VECTOR_RIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_view_stack.push_back(&menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopupMenuScene::PopMenu()
|
||||||
|
{
|
||||||
|
if (!m_view_stack.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_view_stack.size() > 1) {
|
||||||
|
auto previous = m_view_stack[m_view_stack.size() - 2];
|
||||||
|
previous->Show(EASE_VECTOR_RIGHT);
|
||||||
|
previous->InitFocus();
|
||||||
|
m_menus_in_transition.push_back(previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto current = m_view_stack.back();
|
||||||
|
m_view_stack.pop_back();
|
||||||
|
current->Hide(m_view_stack.size() ? EASE_VECTOR_LEFT : EASE_VECTOR_DOWN);
|
||||||
|
m_menus_in_transition.push_back(current);
|
||||||
|
|
||||||
|
if (!m_view_stack.size()) {
|
||||||
|
Hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopupMenuScene::PushFocus()
|
||||||
|
{
|
||||||
|
ImGuiContext *g = ImGui::GetCurrentContext();
|
||||||
|
m_focus_stack.push_back(std::pair<ImGuiID, ImRect>(g->LastItemData.ID,
|
||||||
|
g->LastItemData.Rect));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopupMenuScene::PopFocus()
|
||||||
|
{
|
||||||
|
auto next_focus = m_focus_stack.back();
|
||||||
|
m_focus_stack.pop_back();
|
||||||
|
ImGuiContext *g = ImGui::GetCurrentContext();
|
||||||
|
g->NavInitRequest = false;
|
||||||
|
g->NavInitResultId = next_focus.first;
|
||||||
|
g->NavInitResultRectRel = ImGui::WindowRectAbsToRel(g->CurrentWindow,
|
||||||
|
next_focus.second);
|
||||||
|
// ImGui::NavUpdateAnyRequestFlag();
|
||||||
|
g->NavAnyRequest = g->NavMoveScoringItems || g->NavInitRequest;// || (IMGUI_DEBUG_NAV_SCORING && g->NavWindow != NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopupMenuScene::ClearMenuStack()
|
||||||
|
{
|
||||||
|
if (m_view_stack.size()) {
|
||||||
|
auto current = m_view_stack.back();
|
||||||
|
current->Hide(EASE_VECTOR_DOWN);
|
||||||
|
m_menus_in_transition.push_back(current);
|
||||||
|
}
|
||||||
|
m_view_stack.clear();
|
||||||
|
m_focus_stack.clear();
|
||||||
|
Hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopupMenuScene::HandleInput()
|
||||||
|
{
|
||||||
|
if (IsNavInputPressed(ImGuiNavInput_Cancel)) {
|
||||||
|
PopMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopupMenuScene::Show()
|
||||||
|
{
|
||||||
|
m_background.Show();
|
||||||
|
m_nav_control_view.Show();
|
||||||
|
// m_big_state_icon.Show();
|
||||||
|
// m_title_info.Show();
|
||||||
|
|
||||||
|
if (m_view_stack.size() == 0) {
|
||||||
|
PushMenu(root_menu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopupMenuScene::Hide()
|
||||||
|
{
|
||||||
|
m_background.Hide();
|
||||||
|
m_nav_control_view.Hide();
|
||||||
|
// m_big_state_icon.Hide();
|
||||||
|
// m_title_info.Hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PopupMenuScene::IsAnimating()
|
||||||
|
{
|
||||||
|
return m_menus_in_transition.size() > 0 ||
|
||||||
|
m_background.IsAnimating() ||
|
||||||
|
m_nav_control_view.IsAnimating();
|
||||||
|
// m_big_state_icon.IsAnimating() ||
|
||||||
|
// m_title_info.IsAnimating();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PopupMenuScene::Draw()
|
||||||
|
{
|
||||||
|
m_background.Draw();
|
||||||
|
// m_big_state_icon.Draw();
|
||||||
|
// m_title_info.Draw();
|
||||||
|
|
||||||
|
bool displayed = false;
|
||||||
|
while (m_menus_in_transition.size()) {
|
||||||
|
auto current = m_menus_in_transition.back();
|
||||||
|
if (current->IsAnimating()) {
|
||||||
|
current->Draw(*this);
|
||||||
|
displayed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
m_menus_in_transition.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!displayed) {
|
||||||
|
if (m_view_stack.size()) {
|
||||||
|
m_view_stack.back()->Draw(*this);
|
||||||
|
HandleInput();
|
||||||
|
displayed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_nav_control_view.Draw();
|
||||||
|
return displayed || IsAnimating();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopupMenuScene::LostFocus()
|
||||||
|
{
|
||||||
|
ClearMenuStack();
|
||||||
|
}
|
||||||
|
|
||||||
|
PopupMenuScene g_popup_menu;
|
|
@ -0,0 +1,85 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
#include "common.hh"
|
||||||
|
#include "scene.hh"
|
||||||
|
#include "scene-components.hh"
|
||||||
|
#include "animation.hh"
|
||||||
|
#include "widgets.hh"
|
||||||
|
|
||||||
|
class PopupMenu;
|
||||||
|
|
||||||
|
class PopupMenuItemDelegate
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PopupMenuItemDelegate() = default;
|
||||||
|
virtual ~PopupMenuItemDelegate();
|
||||||
|
virtual void PushMenu(PopupMenu &menu);
|
||||||
|
virtual void PopMenu();
|
||||||
|
virtual void ClearMenuStack();
|
||||||
|
virtual void LostFocus();
|
||||||
|
virtual void PushFocus();
|
||||||
|
virtual void PopFocus();
|
||||||
|
virtual bool DidPop();
|
||||||
|
};
|
||||||
|
|
||||||
|
class PopupMenu
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
EasingAnimation m_animation;
|
||||||
|
ImVec2 m_ease_direction;
|
||||||
|
bool m_focus;
|
||||||
|
bool m_pop_focus;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PopupMenu();
|
||||||
|
void InitFocus();
|
||||||
|
virtual ~PopupMenu();
|
||||||
|
void Show(const ImVec2 &direction);
|
||||||
|
void Hide(const ImVec2 &direction);
|
||||||
|
bool IsAnimating();
|
||||||
|
void Draw(PopupMenuItemDelegate &nav);
|
||||||
|
virtual bool DrawItems(PopupMenuItemDelegate &nav);
|
||||||
|
};
|
||||||
|
|
||||||
|
class PopupMenuScene : virtual public PopupMenuItemDelegate, public Scene {
|
||||||
|
protected:
|
||||||
|
std::vector<PopupMenu *> m_view_stack;
|
||||||
|
std::vector<PopupMenu *> m_menus_in_transition;
|
||||||
|
std::vector<std::pair<ImGuiID, ImRect>> m_focus_stack;
|
||||||
|
BackgroundGradient m_background;
|
||||||
|
NavControlAnnotation m_nav_control_view;
|
||||||
|
// BigStateIcon m_big_state_icon;
|
||||||
|
// TitleInfo m_title_info;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void PushMenu(PopupMenu &menu) override;
|
||||||
|
void PopMenu() override;
|
||||||
|
void PushFocus() override;
|
||||||
|
void PopFocus() override;
|
||||||
|
void ClearMenuStack() override;
|
||||||
|
void HandleInput();
|
||||||
|
void Show() override;
|
||||||
|
void Hide() override;
|
||||||
|
bool IsAnimating() override;
|
||||||
|
bool Draw() override;
|
||||||
|
void LostFocus() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern PopupMenuScene g_popup_menu;
|
|
@ -1,31 +1,30 @@
|
||||||
/*
|
//
|
||||||
* xemu Reporting
|
// xemu Reporting
|
||||||
*
|
//
|
||||||
* Title compatibility and bug report submission.
|
// Title compatibility and bug report submission.
|
||||||
*
|
//
|
||||||
* Copyright (C) 2020-2021 Matt Borgerson
|
// Copyright (C) 2020-2021 Matt Borgerson
|
||||||
*
|
//
|
||||||
* This program is free software; you can redistribute it and/or modify
|
// This program is free software; you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
// it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
* (at your option) any later version.
|
// (at your option) any later version.
|
||||||
*
|
//
|
||||||
* This program is distributed in the hope that it will be useful,
|
// This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
*
|
//
|
||||||
* You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
//
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include <glib/gi18n.h>
|
#include <glib/gi18n.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "xemu-reporting.h"
|
#include "reporting.hh"
|
||||||
#define CPPHTTPLIB_OPENSSL_SUPPORT 1
|
#define CPPHTTPLIB_OPENSSL_SUPPORT 1
|
||||||
#include "httplib.h"
|
#include <httplib.h>
|
||||||
#include "json.hpp"
|
#include <json.hpp>
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
#define DEBUG_COMPAT_SERVICE 0
|
#define DEBUG_COMPAT_SERVICE 0
|
|
@ -1,26 +1,23 @@
|
||||||
/*
|
//
|
||||||
* xemu Reporting
|
// xemu Reporting
|
||||||
*
|
//
|
||||||
* Title compatibility and bug report submission.
|
// Title compatibility and bug report submission.
|
||||||
*
|
//
|
||||||
* Copyright (C) 2020-2021 Matt Borgerson
|
// Copyright (C) 2020-2021 Matt Borgerson
|
||||||
*
|
//
|
||||||
* This program is free software; you can redistribute it and/or modify
|
// This program is free software; you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
// it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
* (at your option) any later version.
|
// (at your option) any later version.
|
||||||
*
|
//
|
||||||
* This program is distributed in the hope that it will be useful,
|
// This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
*
|
//
|
||||||
* You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
#pragma once
|
||||||
|
|
||||||
#ifndef XEMU_REPORTING_H
|
|
||||||
#define XEMU_REPORTING_H
|
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
@ -59,5 +56,3 @@ public:
|
||||||
const std::string &GetSerializedReport();
|
const std::string &GetSerializedReport();
|
||||||
void SetXbeData(struct xbe *xbe);
|
void SetXbeData(struct xbe *xbe);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
|
|
@ -0,0 +1,278 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#include "scene-components.hh"
|
||||||
|
#include "common.hh"
|
||||||
|
#include "misc.hh"
|
||||||
|
#include "font-manager.hh"
|
||||||
|
#include "input-manager.hh"
|
||||||
|
#include "viewport-manager.hh"
|
||||||
|
|
||||||
|
BackgroundGradient::BackgroundGradient()
|
||||||
|
: m_animation(0.2, 0.2) {}
|
||||||
|
|
||||||
|
void BackgroundGradient::Show()
|
||||||
|
{
|
||||||
|
m_animation.EaseIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BackgroundGradient::Hide()
|
||||||
|
{
|
||||||
|
m_animation.EaseOut();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BackgroundGradient::IsAnimating()
|
||||||
|
{
|
||||||
|
return m_animation.IsAnimating();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BackgroundGradient::Draw()
|
||||||
|
{
|
||||||
|
m_animation.Step();
|
||||||
|
|
||||||
|
float a = m_animation.GetSinInterpolatedValue();
|
||||||
|
ImU32 top_color = ImGui::GetColorU32(ImVec4(0,0,0,a));
|
||||||
|
ImU32 bottom_color = ImGui::GetColorU32(ImVec4(0,0,0,fmax(0, fmin(a-0.125, 0.125))));
|
||||||
|
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
auto dl = ImGui::GetBackgroundDrawList();
|
||||||
|
dl->AddRectFilledMultiColor(ImVec2(0, 0), io.DisplaySize, top_color, top_color, bottom_color, bottom_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
NavControlItem::NavControlItem(std::string icon, std::string text)
|
||||||
|
: m_icon(icon), m_text(text) {}
|
||||||
|
|
||||||
|
void NavControlItem::Draw()
|
||||||
|
{
|
||||||
|
ImGui::PushFont(g_font_mgr.m_menu_font_small);
|
||||||
|
auto text = string_format("%s %s", m_icon.c_str(), m_text.c_str());
|
||||||
|
ImGui::Text("%s", text.c_str());
|
||||||
|
ImGui::PopFont();
|
||||||
|
}
|
||||||
|
|
||||||
|
NavControlAnnotation::NavControlAnnotation()
|
||||||
|
: m_animation(0.12,0.12)
|
||||||
|
{
|
||||||
|
m_show = false;
|
||||||
|
m_visible = false;
|
||||||
|
|
||||||
|
// FIXME: Based on controller input type, display different icons. Currently
|
||||||
|
// only showing Xbox scheme
|
||||||
|
// FIXME: Support configuration of displayed items
|
||||||
|
m_items.push_back(NavControlItem(ICON_BUTTON_A, "SELECT"));
|
||||||
|
m_items.push_back(NavControlItem(ICON_BUTTON_B, "BACK"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavControlAnnotation::Show()
|
||||||
|
{
|
||||||
|
m_show = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavControlAnnotation::Hide()
|
||||||
|
{
|
||||||
|
m_show = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NavControlAnnotation::IsAnimating()
|
||||||
|
{
|
||||||
|
return m_animation.IsAnimating();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavControlAnnotation::Draw()
|
||||||
|
{
|
||||||
|
if (g_input_mgr.IsNavigatingWithController() && m_show && !m_visible) {
|
||||||
|
m_animation.EaseIn();
|
||||||
|
m_visible = true;
|
||||||
|
} else if ((!g_input_mgr.IsNavigatingWithController() || !m_show) &&
|
||||||
|
m_visible) {
|
||||||
|
m_animation.EaseOut();
|
||||||
|
m_visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_animation.Step();
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
ImGui::SetNextWindowBgAlpha(0);
|
||||||
|
ImGui::SetNextWindowPos(
|
||||||
|
ImVec2(io.DisplaySize.x - g_viewport_mgr.GetExtents().z,
|
||||||
|
io.DisplaySize.y - g_viewport_mgr.GetExtents().w),
|
||||||
|
ImGuiCond_Always, ImVec2(1, 1));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_Alpha,
|
||||||
|
m_animation.GetSinInterpolatedValue());
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10, 0));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(30, 0));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0.5));
|
||||||
|
if (ImGui::Begin("###NavControlAnnotation", NULL,
|
||||||
|
ImGuiWindowFlags_NoDecoration |
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize |
|
||||||
|
ImGuiWindowFlags_NoSavedSettings |
|
||||||
|
ImGuiWindowFlags_NoFocusOnAppearing |
|
||||||
|
ImGuiWindowFlags_NoInputs)) {
|
||||||
|
int i = 0;
|
||||||
|
for (auto &button : m_items) {
|
||||||
|
if (i++) ImGui::SameLine();
|
||||||
|
button.Draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
ImGui::PopStyleVar(6);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
class BigStateIcon {
|
||||||
|
protected:
|
||||||
|
EasingAnimation m_animation;
|
||||||
|
|
||||||
|
public:
|
||||||
|
BigStateIcon()
|
||||||
|
: m_animation(0.5, 0.15)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Show() {
|
||||||
|
m_animation.easeIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Hide() {
|
||||||
|
m_animation.easeOut();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsAnimating()
|
||||||
|
{
|
||||||
|
return m_animation.IsAnimating();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Draw()
|
||||||
|
{
|
||||||
|
m_animation.step();
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
ImGui::SetNextWindowBgAlpha(0);
|
||||||
|
ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x - g_viewport_mgr.getExtents().z, g_viewport_mgr.getExtents().y),
|
||||||
|
ImGuiCond_Always, ImVec2(1, 0));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, m_animation.getSinInterpolatedValue());
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10*g_viewport_mgr.m_scale, 0));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
|
||||||
|
if (ImGui::Begin("###BigStateIcon", NULL, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings)) {
|
||||||
|
ImGui::PushFont(g_font_mgr.m_bigStateIconFont);
|
||||||
|
ImGui::Text("%s", ICON_FA_PAUSE);
|
||||||
|
ImGui::PopFont();
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
ImGui::PopStyleVar(4);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class TitleInfo
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
GLuint screenshot;
|
||||||
|
ImVec2 size;
|
||||||
|
EasingAnimation m_animation;
|
||||||
|
|
||||||
|
public:
|
||||||
|
TitleInfo()
|
||||||
|
: m_animation(0.2, 0.2)
|
||||||
|
{
|
||||||
|
screenshot = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Show()
|
||||||
|
{
|
||||||
|
m_animation.easeIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Hide()
|
||||||
|
{
|
||||||
|
m_animation.easeOut();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsAnimating()
|
||||||
|
{
|
||||||
|
return m_animation.IsAnimating();
|
||||||
|
}
|
||||||
|
|
||||||
|
void initScreenshot()
|
||||||
|
{
|
||||||
|
if (screenshot == 0) {
|
||||||
|
glGenTextures(1, &screenshot);
|
||||||
|
int w, h, n;
|
||||||
|
stbi_set_flip_vertically_on_load(0);
|
||||||
|
unsigned char *data = stbi_load("./data/cover_front.jpg", &w, &h, &n, 4);
|
||||||
|
assert(data);
|
||||||
|
assert(n == 4 || n == 3);
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, screenshot);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
|
||||||
|
stbi_image_free(data);
|
||||||
|
|
||||||
|
// Fix width
|
||||||
|
float width = 100;
|
||||||
|
float height = width*h/w;
|
||||||
|
size = ImVec2(width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Draw()
|
||||||
|
{
|
||||||
|
initScreenshot();
|
||||||
|
m_animation.step();
|
||||||
|
|
||||||
|
ImGui::SetNextWindowSize(g_viewport_mgr.scale(ImVec2(600, 600)));
|
||||||
|
ImGui::SetNextWindowBgAlpha(0);
|
||||||
|
ImGui::SetNextWindowPos(ImVec2(g_viewport_mgr.getExtents().x,
|
||||||
|
g_viewport_mgr.getExtents().y),
|
||||||
|
ImGuiCond_Always);
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, m_animation.getSinInterpolatedValue());
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10, 0));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0.5));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, g_viewport_mgr.m_scale*6);
|
||||||
|
if (ImGui::Begin("###TitleInfo", NULL, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings)) {
|
||||||
|
ImGui::Columns(2, NULL, false);
|
||||||
|
ImGuiStyle &style = ImGui::GetStyle();
|
||||||
|
ImVec2 scaled_size = g_viewport_mgr.scale(size);
|
||||||
|
ImGui::SetColumnWidth(0, scaled_size.x + style.ItemSpacing.x);
|
||||||
|
ImGui::Dummy(scaled_size);
|
||||||
|
ImVec2 p0 = ImGui::GetItemRectMin();
|
||||||
|
ImVec2 p1 = ImGui::GetItemRectMax();
|
||||||
|
ImDrawList *draw_list = ImGui::GetWindowDrawList();
|
||||||
|
draw_list->AddImageRounded((ImTextureID)screenshot, p0, p1, ImVec2(0, 0), ImVec2(1, 1), ImGui::GetColorU32(ImVec4(1,1,1,m_animation.getSinInterpolatedValue())), 3*g_viewport_mgr.m_scale);
|
||||||
|
|
||||||
|
ImGui::NextColumn();
|
||||||
|
|
||||||
|
ImGui::PushFont(g_font_mgr.m_menuFont);
|
||||||
|
ImGui::Text("Halo: Combat Evolved");
|
||||||
|
ImGui::PopFont();
|
||||||
|
ImGui::PushFont(g_font_mgr.m_menuFontSmall);
|
||||||
|
ImGui::Text("NTSC MS-004");
|
||||||
|
ImGui::PopFont();
|
||||||
|
ImGui::Columns(1);
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
ImGui::PopStyleVar(6);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif
|
|
@ -0,0 +1,91 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include "animation.hh"
|
||||||
|
|
||||||
|
class BackgroundGradient
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
EasingAnimation m_animation;
|
||||||
|
|
||||||
|
public:
|
||||||
|
BackgroundGradient();
|
||||||
|
void Show();
|
||||||
|
void Hide();
|
||||||
|
bool IsAnimating();
|
||||||
|
void Draw();
|
||||||
|
};
|
||||||
|
|
||||||
|
class NavControlItem
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
std::string m_icon;
|
||||||
|
std::string m_text;
|
||||||
|
|
||||||
|
public:
|
||||||
|
NavControlItem(std::string icon, std::string text);
|
||||||
|
void Draw();
|
||||||
|
};
|
||||||
|
|
||||||
|
class NavControlAnnotation
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
EasingAnimation m_animation;
|
||||||
|
std::vector<NavControlItem> m_items;
|
||||||
|
bool m_show, m_visible;
|
||||||
|
|
||||||
|
public:
|
||||||
|
NavControlAnnotation();
|
||||||
|
void Show();
|
||||||
|
void Hide();
|
||||||
|
bool IsAnimating();
|
||||||
|
void Draw();
|
||||||
|
};
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
class BigStateIcon {
|
||||||
|
protected:
|
||||||
|
EasingAnimation m_animation;
|
||||||
|
|
||||||
|
public:
|
||||||
|
BigStateIcon();
|
||||||
|
void Show();
|
||||||
|
void Hide();
|
||||||
|
bool IsAnimating();
|
||||||
|
void Draw();
|
||||||
|
};
|
||||||
|
|
||||||
|
class TitleInfo
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
GLuint screenshot;
|
||||||
|
ImVec2 size;
|
||||||
|
EasingAnimation m_animation;
|
||||||
|
|
||||||
|
public:
|
||||||
|
TitleInfo();
|
||||||
|
void Show();
|
||||||
|
void Hide();
|
||||||
|
bool IsAnimating();
|
||||||
|
void initScreenshot();
|
||||||
|
void Draw();
|
||||||
|
};
|
||||||
|
#endif
|
|
@ -0,0 +1,52 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#include "scene-manager.hh"
|
||||||
|
|
||||||
|
SceneManager g_scene_mgr;
|
||||||
|
|
||||||
|
SceneManager::SceneManager()
|
||||||
|
{
|
||||||
|
m_active_scene = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneManager::PushScene(Scene &scene)
|
||||||
|
{
|
||||||
|
m_scenes.insert(m_scenes.begin(), &scene);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SceneManager::IsDisplayingScene()
|
||||||
|
{
|
||||||
|
return m_active_scene != nullptr || m_scenes.size() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SceneManager::Draw()
|
||||||
|
{
|
||||||
|
if (m_active_scene) {
|
||||||
|
bool finished = !m_active_scene->Draw();
|
||||||
|
if (finished) {
|
||||||
|
m_active_scene = nullptr;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else if (m_scenes.size()) {
|
||||||
|
m_active_scene = m_scenes.back();
|
||||||
|
m_scenes.pop_back();
|
||||||
|
m_active_scene->Show();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
#include <vector>
|
||||||
|
#include "scene.hh"
|
||||||
|
|
||||||
|
class SceneManager
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
Scene *m_active_scene;
|
||||||
|
std::vector<Scene *> m_scenes;
|
||||||
|
|
||||||
|
public:
|
||||||
|
SceneManager();
|
||||||
|
void PushScene(Scene &scene);
|
||||||
|
bool IsDisplayingScene();
|
||||||
|
bool Draw();
|
||||||
|
};
|
||||||
|
|
||||||
|
extern SceneManager g_scene_mgr;
|
|
@ -0,0 +1,25 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#include "scene.hh"
|
||||||
|
|
||||||
|
Scene::~Scene() {}
|
||||||
|
void Scene::Show() {}
|
||||||
|
void Scene::Hide() {}
|
||||||
|
bool Scene::IsAnimating() { return false; }
|
||||||
|
bool Scene::Draw() { return false; }
|
|
@ -0,0 +1,30 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
class Scene
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Scene() = default;
|
||||||
|
virtual ~Scene();
|
||||||
|
virtual void Show();
|
||||||
|
virtual void Hide();
|
||||||
|
virtual bool IsAnimating();
|
||||||
|
virtual bool Draw();
|
||||||
|
};
|
|
@ -0,0 +1,276 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#include "common.hh"
|
||||||
|
#include "update.hh"
|
||||||
|
#include "viewport-manager.hh"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <SDL_filesystem.h>
|
||||||
|
#include "util/miniz/miniz.h"
|
||||||
|
#include "xemu-version.h"
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
const char *version_host = "raw.githubusercontent.com";
|
||||||
|
const char *version_uri = "/mborgerson/xemu/ppa-snapshot/XEMU_VERSION";
|
||||||
|
const char *download_host = "github.com";
|
||||||
|
const char *download_uri = "/mborgerson/xemu/releases/latest/download/xemu-win-release.zip";
|
||||||
|
#else
|
||||||
|
FIXME
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define CPPHTTPLIB_OPENSSL_SUPPORT 1
|
||||||
|
#include <httplib.h>
|
||||||
|
|
||||||
|
#define DPRINTF(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__);
|
||||||
|
|
||||||
|
AutoUpdateWindow update_window;
|
||||||
|
|
||||||
|
AutoUpdateWindow::AutoUpdateWindow()
|
||||||
|
{
|
||||||
|
is_open = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoUpdateWindow::CheckForUpdates()
|
||||||
|
{
|
||||||
|
updater.check_for_update([this](){
|
||||||
|
is_open |= updater.is_update_available();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoUpdateWindow::Draw()
|
||||||
|
{
|
||||||
|
if (!is_open) return;
|
||||||
|
ImGui::SetNextWindowContentSize(ImVec2(550.0f*g_viewport_mgr.m_scale, 0.0f));
|
||||||
|
if (!ImGui::Begin("Update", &is_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::IsWindowAppearing() && !updater.is_update_available()) {
|
||||||
|
updater.check_for_update();
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *status_msg[] = {
|
||||||
|
"",
|
||||||
|
"An error has occured. Try again.",
|
||||||
|
"Checking for update...",
|
||||||
|
"Downloading update...",
|
||||||
|
"Update successful! Restart to launch updated version of xemu."
|
||||||
|
};
|
||||||
|
const char *available_msg[] = {
|
||||||
|
"Update availability unknown.",
|
||||||
|
"This version of xemu is up to date.",
|
||||||
|
"An updated version of xemu is available!",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (updater.get_status() == UPDATER_IDLE) {
|
||||||
|
ImGui::Text(available_msg[updater.get_update_availability()]);
|
||||||
|
} else {
|
||||||
|
ImGui::Text(status_msg[updater.get_status()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updater.is_updating()) {
|
||||||
|
ImGui::ProgressBar(updater.get_update_progress_percentage()/100.0f,
|
||||||
|
ImVec2(-1.0f, 0.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
|
||||||
|
|
||||||
|
float w = (130)*g_viewport_mgr.m_scale;
|
||||||
|
float bw = w + (10)*g_viewport_mgr.m_scale;
|
||||||
|
ImGui::SetCursorPosX(ImGui::GetWindowWidth()-bw);
|
||||||
|
|
||||||
|
if (updater.is_checking_for_update() || updater.is_updating()) {
|
||||||
|
if (ImGui::Button("Cancel", ImVec2(w, 0))) {
|
||||||
|
updater.cancel();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (updater.is_pending_restart()) {
|
||||||
|
if (ImGui::Button("Restart", ImVec2(w, 0))) {
|
||||||
|
updater.restart_to_updated();
|
||||||
|
}
|
||||||
|
} else if (updater.is_update_available()) {
|
||||||
|
if (ImGui::Button("Update", ImVec2(w, 0))) {
|
||||||
|
updater.update();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ImGui::Button("Check for Update", ImVec2(w, 0))) {
|
||||||
|
updater.check_for_update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
Updater::Updater()
|
||||||
|
{
|
||||||
|
m_status = UPDATER_IDLE;
|
||||||
|
m_update_availability = UPDATE_AVAILABILITY_UNKNOWN;
|
||||||
|
m_update_percentage = 0;
|
||||||
|
m_latest_version = "Unknown";
|
||||||
|
m_should_cancel = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Updater::check_for_update(UpdaterCallback on_complete)
|
||||||
|
{
|
||||||
|
if (m_status == UPDATER_IDLE || m_status == UPDATER_ERROR) {
|
||||||
|
m_on_complete = on_complete;
|
||||||
|
qemu_thread_create(&m_thread, "update_worker",
|
||||||
|
&Updater::checker_thread_worker_func,
|
||||||
|
this, QEMU_THREAD_JOINABLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void *Updater::checker_thread_worker_func(void *updater)
|
||||||
|
{
|
||||||
|
((Updater *)updater)->check_for_update_internal();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Updater::check_for_update_internal()
|
||||||
|
{
|
||||||
|
httplib::SSLClient cli(version_host, 443);
|
||||||
|
cli.set_follow_location(true);
|
||||||
|
cli.set_timeout_sec(5);
|
||||||
|
auto res = cli.Get(version_uri, [this](uint64_t len, uint64_t total) {
|
||||||
|
m_update_percentage = len*100/total;
|
||||||
|
return !m_should_cancel;
|
||||||
|
});
|
||||||
|
if (m_should_cancel) {
|
||||||
|
m_should_cancel = false;
|
||||||
|
m_status = UPDATER_IDLE;
|
||||||
|
goto finished;
|
||||||
|
} else if (!res || res->status != 200) {
|
||||||
|
m_status = UPDATER_ERROR;
|
||||||
|
goto finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(xemu_version, res->body.c_str())) {
|
||||||
|
m_update_availability = UPDATE_AVAILABLE;
|
||||||
|
} else {
|
||||||
|
m_update_availability = UPDATE_NOT_AVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_latest_version = res->body;
|
||||||
|
m_status = UPDATER_IDLE;
|
||||||
|
finished:
|
||||||
|
if (m_on_complete) {
|
||||||
|
m_on_complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Updater::update()
|
||||||
|
{
|
||||||
|
if (m_status == UPDATER_IDLE || m_status == UPDATER_ERROR) {
|
||||||
|
m_status = UPDATER_UPDATING;
|
||||||
|
qemu_thread_create(&m_thread, "update_worker",
|
||||||
|
&Updater::update_thread_worker_func,
|
||||||
|
this, QEMU_THREAD_JOINABLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void *Updater::update_thread_worker_func(void *updater)
|
||||||
|
{
|
||||||
|
((Updater *)updater)->update_internal();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Updater::update_internal()
|
||||||
|
{
|
||||||
|
httplib::SSLClient cli(download_host, 443);
|
||||||
|
cli.set_follow_location(true);
|
||||||
|
cli.set_timeout_sec(5);
|
||||||
|
auto res = cli.Get(download_uri, [this](uint64_t len, uint64_t total) {
|
||||||
|
m_update_percentage = len*100/total;
|
||||||
|
return !m_should_cancel;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (m_should_cancel) {
|
||||||
|
m_should_cancel = false;
|
||||||
|
m_status = UPDATER_IDLE;
|
||||||
|
return;
|
||||||
|
} else if (!res || res->status != 200) {
|
||||||
|
m_status = UPDATER_ERROR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mz_zip_archive zip;
|
||||||
|
mz_zip_zero_struct(&zip);
|
||||||
|
if (!mz_zip_reader_init_mem(&zip, res->body.data(), res->body.size(), 0)) {
|
||||||
|
DPRINTF("mz_zip_reader_init_mem failed\n");
|
||||||
|
m_status = UPDATER_ERROR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mz_uint num_files = mz_zip_reader_get_num_files(&zip);
|
||||||
|
for (mz_uint file_idx = 0; file_idx < num_files; file_idx++) {
|
||||||
|
mz_zip_archive_file_stat fstat;
|
||||||
|
if (!mz_zip_reader_file_stat(&zip, file_idx, &fstat)) {
|
||||||
|
DPRINTF("mz_zip_reader_file_stat failed for file #%d\n", file_idx);
|
||||||
|
goto errored;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fstat.m_filename[strlen(fstat.m_filename)-1] == '/') {
|
||||||
|
/* FIXME: mkdirs */
|
||||||
|
DPRINTF("FIXME: subdirs not handled yet\n");
|
||||||
|
goto errored;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *dst_path = g_strdup_printf("%s%s", SDL_GetBasePath(), fstat.m_filename);
|
||||||
|
DPRINTF("extracting %s to %s\n", fstat.m_filename, dst_path);
|
||||||
|
|
||||||
|
if (!strcmp(fstat.m_filename, "xemu.exe")) {
|
||||||
|
// We cannot overwrite current executable, but we can move it
|
||||||
|
char *renamed_path = g_strdup_printf("%s%s", SDL_GetBasePath(), "xemu-previous.exe");
|
||||||
|
MoveFileExA(dst_path, renamed_path, MOVEFILE_REPLACE_EXISTING);
|
||||||
|
g_free(renamed_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mz_zip_reader_extract_to_file(&zip, file_idx, dst_path, 0)) {
|
||||||
|
DPRINTF("mz_zip_reader_extract_to_file failed to create %s\n", dst_path);
|
||||||
|
g_free(dst_path);
|
||||||
|
goto errored;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(dst_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_status = UPDATER_UPDATE_SUCCESSFUL;
|
||||||
|
goto cleanup_zip;
|
||||||
|
errored:
|
||||||
|
m_status = UPDATER_ERROR;
|
||||||
|
cleanup_zip:
|
||||||
|
mz_zip_reader_end(&zip);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
extern char **gArgv;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Updater::restart_to_updated()
|
||||||
|
{
|
||||||
|
char *target_exec = g_strdup_printf("%s%s", SDL_GetBasePath(), "xemu.exe");
|
||||||
|
DPRINTF("Restarting to updated executable %s\n", target_exec);
|
||||||
|
_execv(target_exec, gArgv);
|
||||||
|
DPRINTF("Launching updated executable failed\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#include <string>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "qemu/osdep.h"
|
||||||
|
#include "qemu-common.h"
|
||||||
|
#include "qemu/thread.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
UPDATE_AVAILABILITY_UNKNOWN,
|
||||||
|
UPDATE_NOT_AVAILABLE,
|
||||||
|
UPDATE_AVAILABLE
|
||||||
|
} UpdateAvailability;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
UPDATER_IDLE,
|
||||||
|
UPDATER_ERROR,
|
||||||
|
UPDATER_CHECKING_FOR_UPDATE,
|
||||||
|
UPDATER_UPDATING,
|
||||||
|
UPDATER_UPDATE_SUCCESSFUL
|
||||||
|
} UpdateStatus;
|
||||||
|
|
||||||
|
using UpdaterCallback = std::function<void(void)>;
|
||||||
|
|
||||||
|
class Updater {
|
||||||
|
private:
|
||||||
|
UpdateAvailability m_update_availability;
|
||||||
|
int m_update_percentage;
|
||||||
|
QemuThread m_thread;
|
||||||
|
std::string m_latest_version;
|
||||||
|
bool m_should_cancel;
|
||||||
|
UpdateStatus m_status;
|
||||||
|
UpdaterCallback m_on_complete;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Updater();
|
||||||
|
UpdateStatus get_status() { return m_status; }
|
||||||
|
UpdateAvailability get_update_availability() { return m_update_availability; }
|
||||||
|
bool is_errored() { return m_status == UPDATER_ERROR; }
|
||||||
|
bool is_pending_restart() { return m_status == UPDATER_UPDATE_SUCCESSFUL; }
|
||||||
|
bool is_update_available() { return m_update_availability == UPDATE_AVAILABLE; }
|
||||||
|
bool is_checking_for_update() { return m_status == UPDATER_CHECKING_FOR_UPDATE; }
|
||||||
|
bool is_updating() { return m_status == UPDATER_UPDATING; }
|
||||||
|
std::string get_update_version() { return m_latest_version; }
|
||||||
|
void cancel() { m_should_cancel = true; }
|
||||||
|
void update();
|
||||||
|
void update_internal();
|
||||||
|
void check_for_update(UpdaterCallback on_complete = nullptr);
|
||||||
|
void check_for_update_internal();
|
||||||
|
int get_update_progress_percentage() { return m_update_percentage; }
|
||||||
|
static void *update_thread_worker_func(void *updater);
|
||||||
|
static void *checker_thread_worker_func(void *updater);
|
||||||
|
void restart_to_updated(void);
|
||||||
|
};
|
||||||
|
|
||||||
|
class AutoUpdateWindow
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
Updater updater;
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool is_open;
|
||||||
|
|
||||||
|
AutoUpdateWindow();
|
||||||
|
void CheckForUpdates();
|
||||||
|
void Draw();
|
||||||
|
};
|
||||||
|
|
||||||
|
extern AutoUpdateWindow update_window;
|
||||||
|
#endif //_ WIN32
|
|
@ -0,0 +1,89 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#include "viewport-manager.hh"
|
||||||
|
|
||||||
|
ViewportManager g_viewport_mgr;
|
||||||
|
|
||||||
|
ViewportManager::ViewportManager() {
|
||||||
|
m_scale = 1;
|
||||||
|
m_extents.x = 25 * m_scale; // Distance from Left
|
||||||
|
m_extents.y = 25 * m_scale; // '' Top
|
||||||
|
m_extents.z = 25 * m_scale; // '' Right
|
||||||
|
m_extents.w = 25 * m_scale; // '' Bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec4 ViewportManager::GetExtents()
|
||||||
|
{
|
||||||
|
return m_extents;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
void ViewportManager::DrawExtents()
|
||||||
|
{
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
ImVec2 tl(m_extents.x, m_extents.y);
|
||||||
|
ImVec2 tr(io.DisplaySize.x - m_extents.z, m_extents.y);
|
||||||
|
ImVec2 br(io.DisplaySize.x - m_extents.z, io.DisplaySize.y - m_extents.w);
|
||||||
|
ImVec2 bl(m_extents.x, io.DisplaySize.y - m_extents.w);
|
||||||
|
|
||||||
|
auto dl = ImGui::GetForegroundDrawList();
|
||||||
|
ImU32 color = 0xffff00ff;
|
||||||
|
dl->AddLine(tl, tr, color, 2.0);
|
||||||
|
dl->AddLine(tr, br, color, 2.0);
|
||||||
|
dl->AddLine(br, bl, color, 2.0);
|
||||||
|
dl->AddLine(bl, tl, color, 2.0);
|
||||||
|
dl->AddLine(tl, br, color, 2.0);
|
||||||
|
dl->AddLine(bl, tr, color, 2.0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ImVec2 ViewportManager::Scale(const ImVec2 vec2)
|
||||||
|
{
|
||||||
|
return ImVec2(vec2.x * m_scale, vec2.y * m_scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewportManager::Update()
|
||||||
|
{
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
|
||||||
|
if (g_config.display.ui.auto_scale) {
|
||||||
|
if (io.DisplaySize.x > 1920) {
|
||||||
|
g_config.display.ui.scale = 2;
|
||||||
|
} else {
|
||||||
|
g_config.display.ui.scale = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_scale = g_config.display.ui.scale;
|
||||||
|
|
||||||
|
if (m_scale < 1) {
|
||||||
|
m_scale = 1;
|
||||||
|
} else if (m_scale > 2) {
|
||||||
|
m_scale = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (io.DisplaySize.x > 640*m_scale) {
|
||||||
|
m_extents.x = 25 * m_scale; // Distance from Left
|
||||||
|
m_extents.y = 25 * m_scale; // '' Top
|
||||||
|
m_extents.z = 25 * m_scale; // '' Right
|
||||||
|
m_extents.w = 25 * m_scale; // '' Bottom
|
||||||
|
} else {
|
||||||
|
m_extents = ImVec4(0,0,0,0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
#include "common.hh"
|
||||||
|
|
||||||
|
class ViewportManager
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
ImVec4 m_extents;
|
||||||
|
|
||||||
|
public:
|
||||||
|
float m_scale;
|
||||||
|
ViewportManager();
|
||||||
|
ImVec4 GetExtents();
|
||||||
|
void DrawExtents();
|
||||||
|
ImVec2 Scale(const ImVec2 vec2);
|
||||||
|
void Update();
|
||||||
|
};
|
||||||
|
|
||||||
|
extern ViewportManager g_viewport_mgr;
|
|
@ -0,0 +1,99 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#include "ui/xui/viewport-manager.hh"
|
||||||
|
#include "common.hh"
|
||||||
|
#include "imgui.h"
|
||||||
|
#include "viewport-manager.hh"
|
||||||
|
#include "welcome.hh"
|
||||||
|
#include "widgets.hh"
|
||||||
|
#include "misc.hh"
|
||||||
|
#include "gl-helpers.hh"
|
||||||
|
#include "xemu-version.h"
|
||||||
|
#include "main-menu.hh"
|
||||||
|
|
||||||
|
FirstBootWindow::FirstBootWindow()
|
||||||
|
{
|
||||||
|
is_open = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FirstBootWindow::Draw()
|
||||||
|
{
|
||||||
|
if (!is_open) return;
|
||||||
|
|
||||||
|
ImVec2 size(400*g_viewport_mgr.m_scale, 300*g_viewport_mgr.m_scale);
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
|
||||||
|
ImVec2 window_pos = ImVec2((io.DisplaySize.x - size.x)/2, (io.DisplaySize.y - size.y)/2);
|
||||||
|
ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always);
|
||||||
|
|
||||||
|
ImGui::SetNextWindowSize(size, ImGuiCond_Appearing);
|
||||||
|
if (!ImGui::Begin("First Boot", &is_open, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDecoration)) {
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t time_start = 0;
|
||||||
|
if (ImGui::IsWindowAppearing()) {
|
||||||
|
time_start = SDL_GetTicks();
|
||||||
|
}
|
||||||
|
uint32_t now = SDL_GetTicks() - time_start;
|
||||||
|
|
||||||
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY()-50*g_viewport_mgr.m_scale);
|
||||||
|
ImGui::SetCursorPosX((ImGui::GetWindowWidth()-256*g_viewport_mgr.m_scale)/2);
|
||||||
|
|
||||||
|
logo_fbo->Target();
|
||||||
|
ImTextureID id = (ImTextureID)(intptr_t)logo_fbo->Texture();
|
||||||
|
float t_w = 256.0;
|
||||||
|
float t_h = 256.0;
|
||||||
|
float x_off = 0;
|
||||||
|
ImGui::Image(id,
|
||||||
|
ImVec2((t_w-x_off)*g_viewport_mgr.m_scale, t_h*g_viewport_mgr.m_scale),
|
||||||
|
ImVec2(x_off/t_w, t_h/t_h),
|
||||||
|
ImVec2(t_w/t_w, 0));
|
||||||
|
if (ImGui::IsItemClicked()) {
|
||||||
|
time_start = SDL_GetTicks();
|
||||||
|
}
|
||||||
|
RenderLogo(now, 0x42e335ff, 0x42e335ff, 0x00000000);
|
||||||
|
logo_fbo->Restore();
|
||||||
|
|
||||||
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY()-100*g_viewport_mgr.m_scale);
|
||||||
|
ImGui::SetCursorPosX(10*g_viewport_mgr.m_scale);
|
||||||
|
ImGui::Dummy(ImVec2(0,20*g_viewport_mgr.m_scale));
|
||||||
|
|
||||||
|
const char *msg = "Configure machine settings to get started";
|
||||||
|
ImGui::SetCursorPosX((ImGui::GetWindowWidth()-ImGui::CalcTextSize(msg).x)/2);
|
||||||
|
ImGui::Text("%s", msg);
|
||||||
|
|
||||||
|
ImGui::Dummy(ImVec2(0,20*g_viewport_mgr.m_scale));
|
||||||
|
ImGui::SetCursorPosX((ImGui::GetWindowWidth()-120*g_viewport_mgr.m_scale)/2);
|
||||||
|
ImGui::SetItemDefaultFocus();
|
||||||
|
if (ImGui::Button("Settings", ImVec2(120*g_viewport_mgr.m_scale, 0))) {
|
||||||
|
g_main_menu.ShowSystem();
|
||||||
|
g_config.general.show_welcome = false;
|
||||||
|
}
|
||||||
|
ImGui::Dummy(ImVec2(0,20*g_viewport_mgr.m_scale));
|
||||||
|
|
||||||
|
msg = "Visit https://xemu.app for more information";
|
||||||
|
ImGui::SetCursorPosX((ImGui::GetWindowWidth()-ImGui::CalcTextSize(msg).x)/2);
|
||||||
|
Hyperlink(msg, "https://xemu.app");
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
FirstBootWindow first_boot_window;
|
|
@ -0,0 +1,29 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
class FirstBootWindow
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool is_open;
|
||||||
|
FirstBootWindow();
|
||||||
|
void Draw();
|
||||||
|
};
|
||||||
|
|
||||||
|
extern FirstBootWindow first_boot_window;
|
|
@ -0,0 +1,506 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#include "widgets.hh"
|
||||||
|
#include "misc.hh"
|
||||||
|
#include "font-manager.hh"
|
||||||
|
#include "viewport-manager.hh"
|
||||||
|
#include "ui/xemu-os-utils.h"
|
||||||
|
|
||||||
|
void Separator()
|
||||||
|
{
|
||||||
|
// XXX: IDK. Maybe there's a better way to draw a separator ( ImGui::Separator() ) that cuts through window
|
||||||
|
// padding... Just grab the draw list and draw the line with outer clip rect
|
||||||
|
|
||||||
|
float thickness = 1 * g_viewport_mgr.m_scale;
|
||||||
|
|
||||||
|
ImGuiWindow *window = ImGui::GetCurrentWindow();
|
||||||
|
ImDrawList *draw_list = ImGui::GetWindowDrawList();
|
||||||
|
ImRect window_rect = window->Rect();
|
||||||
|
ImVec2 size = ImVec2(window_rect.GetWidth(), thickness);
|
||||||
|
|
||||||
|
ImVec2 p0(window_rect.Min.x, ImGui::GetCursorScreenPos().y);
|
||||||
|
ImVec2 p1(p0.x + size.x, p0.y);
|
||||||
|
ImGui::PushClipRect(window_rect.Min, window_rect.Max, false);
|
||||||
|
draw_list->AddLine(p0, p1, ImGui::GetColorU32(ImGuiCol_Separator), thickness);
|
||||||
|
ImGui::PopClipRect();
|
||||||
|
ImGui::Dummy(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SectionTitle(const char *title)
|
||||||
|
{
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::PushFont(g_font_mgr.m_menu_font_medium);
|
||||||
|
ImGui::Text("%s", title);
|
||||||
|
ImGui::PopFont();
|
||||||
|
Separator();
|
||||||
|
}
|
||||||
|
|
||||||
|
float GetWidgetTitleDescriptionHeight(const char *title,
|
||||||
|
const char *description)
|
||||||
|
{
|
||||||
|
ImGui::PushFont(g_font_mgr.m_menu_font_medium);
|
||||||
|
float h = ImGui::GetFrameHeight();
|
||||||
|
ImGui::PopFont();
|
||||||
|
|
||||||
|
if (description) {
|
||||||
|
ImGuiStyle &style = ImGui::GetStyle();
|
||||||
|
h += style.ItemInnerSpacing.y;
|
||||||
|
ImGui::PushFont(g_font_mgr.m_default_font);
|
||||||
|
h += ImGui::GetTextLineHeight();
|
||||||
|
ImGui::PopFont();
|
||||||
|
}
|
||||||
|
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WidgetTitleDescription(const char *title, const char *description,
|
||||||
|
ImVec2 pos)
|
||||||
|
{
|
||||||
|
ImDrawList *draw_list = ImGui::GetWindowDrawList();
|
||||||
|
ImGuiStyle &style = ImGui::GetStyle();
|
||||||
|
|
||||||
|
ImVec2 text_pos = pos;
|
||||||
|
text_pos.x += style.FramePadding.x;
|
||||||
|
text_pos.y += style.FramePadding.y;
|
||||||
|
|
||||||
|
ImGui::PushFont(g_font_mgr.m_menu_font_medium);
|
||||||
|
float title_height = ImGui::GetTextLineHeight();
|
||||||
|
draw_list->AddText(text_pos, ImGui::GetColorU32(ImGuiCol_Text), title);
|
||||||
|
ImGui::PopFont();
|
||||||
|
|
||||||
|
if (description) {
|
||||||
|
text_pos.y += title_height + style.ItemInnerSpacing.y;
|
||||||
|
|
||||||
|
ImGui::PushFont(g_font_mgr.m_default_font);
|
||||||
|
draw_list->AddText(text_pos, ImGui::GetColorU32(ImVec4(0.94f, 0.94f, 0.94f, 0.70f)), description);
|
||||||
|
ImGui::PopFont();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WidgetTitleDescriptionItem(const char *str_id, const char *description)
|
||||||
|
{
|
||||||
|
ImVec2 p = ImGui::GetCursorScreenPos();
|
||||||
|
ImVec2 size(ImGui::GetColumnWidth(),
|
||||||
|
GetWidgetTitleDescriptionHeight(str_id, description));
|
||||||
|
WidgetTitleDescription(str_id, description, p);
|
||||||
|
|
||||||
|
// XXX: Internal API
|
||||||
|
ImRect bb(p, ImVec2(p.x + size.x, p.y + size.y));
|
||||||
|
ImGui::ItemSize(size, 0.0f);
|
||||||
|
ImGui::ItemAdd(bb, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float GetSliderRadius(ImVec2 size)
|
||||||
|
{
|
||||||
|
return size.y * 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
float GetSliderTrackXOffset(ImVec2 size)
|
||||||
|
{
|
||||||
|
return GetSliderRadius(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
float GetSliderTrackWidth(ImVec2 size)
|
||||||
|
{
|
||||||
|
return size.x - GetSliderRadius(size) * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
float GetSliderValueForMousePos(ImVec2 mouse, ImVec2 pos, ImVec2 size)
|
||||||
|
{
|
||||||
|
return (mouse.x - pos.x - GetSliderTrackXOffset(size)) /
|
||||||
|
GetSliderTrackWidth(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawSlider(float v, bool hovered, ImVec2 pos, ImVec2 size)
|
||||||
|
{
|
||||||
|
ImDrawList *draw_list = ImGui::GetWindowDrawList();
|
||||||
|
|
||||||
|
float radius = GetSliderRadius(size);
|
||||||
|
float rounding = size.y * 0.25;
|
||||||
|
float slot_half_height = size.y * 0.125;
|
||||||
|
const bool circular_grab = false;
|
||||||
|
|
||||||
|
ImU32 bg = hovered ? ImGui::GetColorU32(ImGuiCol_FrameBgActive)
|
||||||
|
: ImGui::GetColorU32(ImGuiCol_CheckMark);
|
||||||
|
|
||||||
|
ImVec2 pmid(pos.x + radius + v*(size.x - radius*2), pos.y + size.y / 2);
|
||||||
|
ImVec2 smin(pos.x + rounding, pmid.y - slot_half_height);
|
||||||
|
ImVec2 smax(pmid.x, pmid.y + slot_half_height);
|
||||||
|
draw_list->AddRectFilled(smin, smax, bg, rounding);
|
||||||
|
|
||||||
|
bg = hovered ? ImGui::GetColorU32(ImGuiCol_FrameBgHovered)
|
||||||
|
: ImGui::GetColorU32(ImGuiCol_FrameBg);
|
||||||
|
|
||||||
|
smin.x = pmid.x;
|
||||||
|
smax.x = pos.x + size.x - rounding;
|
||||||
|
draw_list->AddRectFilled(smin, smax, bg, rounding);
|
||||||
|
|
||||||
|
if (circular_grab) {
|
||||||
|
draw_list->AddCircleFilled(pmid, radius * 0.8, ImGui::GetColorU32(ImGuiCol_SliderGrab));
|
||||||
|
} else {
|
||||||
|
ImVec2 offs(radius*0.8, radius*0.8);
|
||||||
|
draw_list->AddRectFilled(pmid - offs, pmid + offs, ImGui::GetColorU32(ImGuiCol_SliderGrab), rounding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawToggle(bool enabled, bool hovered, ImVec2 pos, ImVec2 size)
|
||||||
|
{
|
||||||
|
ImDrawList *draw_list = ImGui::GetWindowDrawList();
|
||||||
|
|
||||||
|
float radius = size.y * 0.5;
|
||||||
|
float rounding = size.y * 0.25;
|
||||||
|
float slot_half_height = size.y * 0.5;
|
||||||
|
const bool circular_grab = false;
|
||||||
|
|
||||||
|
ImU32 bg = hovered ? ImGui::GetColorU32(enabled ? ImGuiCol_FrameBgActive : ImGuiCol_FrameBgHovered)
|
||||||
|
: ImGui::GetColorU32(enabled ? ImGuiCol_CheckMark : ImGuiCol_FrameBg);
|
||||||
|
|
||||||
|
ImVec2 pmid(pos.x + radius + (int)enabled * (size.x - radius * 2), pos.y + size.y / 2);
|
||||||
|
ImVec2 smin(pos.x, pmid.y - slot_half_height);
|
||||||
|
ImVec2 smax(pos.x + size.x, pmid.y + slot_half_height);
|
||||||
|
draw_list->AddRectFilled(smin, smax, bg, rounding);
|
||||||
|
|
||||||
|
if (circular_grab) {
|
||||||
|
draw_list->AddCircleFilled(pmid, radius * 0.8, ImGui::GetColorU32(ImGuiCol_SliderGrab));
|
||||||
|
} else {
|
||||||
|
ImVec2 offs(radius*0.8, radius*0.8);
|
||||||
|
draw_list->AddRectFilled(pmid - offs, pmid + offs, ImGui::GetColorU32(ImGuiCol_SliderGrab), rounding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Toggle(const char *str_id, bool *v, const char *description)
|
||||||
|
{
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS);
|
||||||
|
|
||||||
|
ImGuiStyle &style = ImGui::GetStyle();
|
||||||
|
|
||||||
|
ImGui::PushFont(g_font_mgr.m_menu_font_medium);
|
||||||
|
float title_height = ImGui::GetTextLineHeight();
|
||||||
|
ImGui::PopFont();
|
||||||
|
|
||||||
|
ImVec2 p = ImGui::GetCursorScreenPos();
|
||||||
|
ImVec2 bb(ImGui::GetColumnWidth(),
|
||||||
|
GetWidgetTitleDescriptionHeight(str_id, description));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0));
|
||||||
|
ImGui::PushID(str_id);
|
||||||
|
bool status = ImGui::Button("###toggle_button", bb);
|
||||||
|
if (status) {
|
||||||
|
*v = !*v;
|
||||||
|
}
|
||||||
|
ImGui::PopID();
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
const ImVec2 p_min = ImGui::GetItemRectMin();
|
||||||
|
const ImVec2 p_max = ImGui::GetItemRectMax();
|
||||||
|
|
||||||
|
WidgetTitleDescription(str_id, description, p);
|
||||||
|
|
||||||
|
float toggle_height = title_height * 0.9;
|
||||||
|
ImVec2 toggle_size(toggle_height * 1.75, toggle_height);
|
||||||
|
ImVec2 toggle_pos(p_max.x - toggle_size.x - style.FramePadding.x,
|
||||||
|
p_min.y + (title_height - toggle_size.y)/2 + style.FramePadding.y);
|
||||||
|
DrawToggle(*v, ImGui::IsItemHovered(), toggle_pos, toggle_size);
|
||||||
|
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Slider(const char *str_id, float *v, const char *description)
|
||||||
|
{
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS);
|
||||||
|
|
||||||
|
ImGuiStyle &style = ImGui::GetStyle();
|
||||||
|
ImGuiWindow *window = ImGui::GetCurrentWindow();
|
||||||
|
|
||||||
|
ImGui::PushFont(g_font_mgr.m_menu_font_medium);
|
||||||
|
float title_height = ImGui::GetTextLineHeight();
|
||||||
|
ImGui::PopFont();
|
||||||
|
|
||||||
|
ImVec2 p = ImGui::GetCursorScreenPos();
|
||||||
|
ImVec2 size(ImGui::GetColumnWidth(),
|
||||||
|
GetWidgetTitleDescriptionHeight(str_id, description));
|
||||||
|
WidgetTitleDescription(str_id, description, p);
|
||||||
|
|
||||||
|
// XXX: Internal API
|
||||||
|
ImVec2 wpos = ImGui::GetCursorPos();
|
||||||
|
ImRect bb(p, ImVec2(p.x + size.x, p.y + size.y));
|
||||||
|
ImGui::ItemSize(size, 0.0f);
|
||||||
|
ImGui::ItemAdd(bb, 0);
|
||||||
|
ImGui::SetItemAllowOverlap();
|
||||||
|
ImGui::SameLine(0, 0);
|
||||||
|
|
||||||
|
ImVec2 slider_size(size.x * 0.4, title_height * 0.9);
|
||||||
|
ImVec2 slider_pos(bb.Max.x - slider_size.x - style.FramePadding.x,
|
||||||
|
p.y + (title_height - slider_size.y)/2 + style.FramePadding.y);
|
||||||
|
|
||||||
|
ImGui::SetCursorPos(ImVec2(wpos.x + size.x - slider_size.x - style.FramePadding.x,
|
||||||
|
wpos.y));
|
||||||
|
|
||||||
|
ImGui::InvisibleButton("###slider", slider_size, 0);
|
||||||
|
|
||||||
|
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow) ||
|
||||||
|
ImGui::IsKeyPressed(ImGuiKey_GamepadDpadLeft) ||
|
||||||
|
ImGui::IsKeyPressed(ImGuiKey_GamepadLStickLeft) ||
|
||||||
|
ImGui::IsKeyPressed(ImGuiKey_GamepadRStickLeft)) {
|
||||||
|
*v -= 0.05;
|
||||||
|
}
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_RightArrow) ||
|
||||||
|
ImGui::IsKeyPressed(ImGuiKey_GamepadDpadRight) ||
|
||||||
|
ImGui::IsKeyPressed(ImGuiKey_GamepadLStickRight) ||
|
||||||
|
ImGui::IsKeyPressed(ImGuiKey_GamepadRStickRight)) {
|
||||||
|
*v += 0.05;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
ImGui::IsKeyDown(ImGuiKey_LeftArrow) ||
|
||||||
|
ImGui::IsKeyDown(ImGuiKey_GamepadDpadLeft) ||
|
||||||
|
ImGui::IsKeyDown(ImGuiKey_GamepadLStickLeft) ||
|
||||||
|
ImGui::IsKeyDown(ImGuiKey_GamepadRStickLeft) ||
|
||||||
|
ImGui::IsKeyDown(ImGuiKey_RightArrow) ||
|
||||||
|
ImGui::IsKeyDown(ImGuiKey_GamepadDpadRight) ||
|
||||||
|
ImGui::IsKeyDown(ImGuiKey_GamepadLStickRight) ||
|
||||||
|
ImGui::IsKeyDown(ImGuiKey_GamepadRStickRight)
|
||||||
|
) {
|
||||||
|
ImGui::NavMoveRequestCancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::IsItemActive()) {
|
||||||
|
ImVec2 mouse = ImGui::GetMousePos();
|
||||||
|
*v = GetSliderValueForMousePos(mouse, slider_pos, slider_size);
|
||||||
|
}
|
||||||
|
*v = fmax(0, fmin(*v, 1));
|
||||||
|
DrawSlider(*v, ImGui::IsItemHovered() || ImGui::IsItemActive(), slider_pos,
|
||||||
|
slider_size);
|
||||||
|
|
||||||
|
ImVec2 slider_max = ImVec2(slider_pos.x + slider_size.x, slider_pos.y + slider_size.y);
|
||||||
|
ImGui::RenderNavHighlight(ImRect(slider_pos, slider_max), window->GetID("###slider"));
|
||||||
|
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FilePicker(const char *str_id, const char **buf, const char *filters,
|
||||||
|
bool dir)
|
||||||
|
{
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS);
|
||||||
|
ImGuiStyle &style = ImGui::GetStyle();
|
||||||
|
ImVec2 p = ImGui::GetCursorScreenPos();
|
||||||
|
const char *desc = strlen(*buf) ? *buf : "(None Selected)";
|
||||||
|
ImVec2 bb(ImGui::GetColumnWidth(),
|
||||||
|
GetWidgetTitleDescriptionHeight(str_id, desc));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0));
|
||||||
|
ImGui::PushID(str_id);
|
||||||
|
bool status = ImGui::Button("###file_button", bb);
|
||||||
|
if (status) {
|
||||||
|
const char *new_path =
|
||||||
|
PausedFileOpen(dir ? NOC_FILE_DIALOG_DIR : NOC_FILE_DIALOG_OPEN,
|
||||||
|
filters, *buf, NULL);
|
||||||
|
if (new_path) {
|
||||||
|
free((void*)*buf);
|
||||||
|
*buf = strdup(new_path);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::PopID();
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
|
||||||
|
WidgetTitleDescription(str_id, desc, p);
|
||||||
|
|
||||||
|
const ImVec2 p0 = ImGui::GetItemRectMin();
|
||||||
|
const ImVec2 p1 = ImGui::GetItemRectMax();
|
||||||
|
|
||||||
|
ImDrawList *draw_list = ImGui::GetWindowDrawList();
|
||||||
|
|
||||||
|
ImGui::PushFont(g_font_mgr.m_menu_font);
|
||||||
|
const char *icon = dir ? ICON_FA_FOLDER : ICON_FA_FILE;
|
||||||
|
ImVec2 ts_icon = ImGui::CalcTextSize(icon);
|
||||||
|
draw_list->AddText(ImVec2(p1.x - style.FramePadding.x - ts_icon.x,
|
||||||
|
p0.y + (p1.y - p0.y - ts_icon.y) / 2),
|
||||||
|
ImGui::GetColorU32(ImGuiCol_Text), icon);
|
||||||
|
ImGui::PopFont();
|
||||||
|
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawComboChevron()
|
||||||
|
{
|
||||||
|
ImGui::PushFont(g_font_mgr.m_menu_font);
|
||||||
|
const ImVec2 p0 = ImGui::GetItemRectMin();
|
||||||
|
const ImVec2 p1 = ImGui::GetItemRectMax();
|
||||||
|
const char *icon = ICON_FA_CHEVRON_DOWN;
|
||||||
|
ImVec2 ts_icon = ImGui::CalcTextSize(icon);
|
||||||
|
ImGuiStyle &style = ImGui::GetStyle();
|
||||||
|
ImDrawList *draw_list = ImGui::GetWindowDrawList();
|
||||||
|
draw_list->AddText(ImVec2(p1.x - style.FramePadding.x - ts_icon.x,
|
||||||
|
p0.y + (p1.y - p0.y - ts_icon.y) / 2),
|
||||||
|
ImGui::GetColorU32(ImGuiCol_Text), icon);
|
||||||
|
ImGui::PopFont();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrepareComboTitleDescription(const char *label, const char *description,
|
||||||
|
float combo_size_ratio)
|
||||||
|
{
|
||||||
|
float width = ImGui::GetColumnWidth();
|
||||||
|
ImVec2 pos = ImGui::GetCursorScreenPos();
|
||||||
|
ImVec2 size(width, GetWidgetTitleDescriptionHeight(label, description));
|
||||||
|
WidgetTitleDescription(label, description, pos);
|
||||||
|
|
||||||
|
ImVec2 wpos = ImGui::GetCursorPos();
|
||||||
|
ImRect bb(pos, ImVec2(pos.x + size.x, pos.y + size.y));
|
||||||
|
ImGui::ItemSize(size, 0.0f);
|
||||||
|
ImGui::ItemAdd(bb, 0);
|
||||||
|
ImGui::SetItemAllowOverlap();
|
||||||
|
ImGui::SameLine(0, 0);
|
||||||
|
float combo_width = width * combo_size_ratio;
|
||||||
|
ImGui::SetCursorPos(ImVec2(wpos.x + width - combo_width, wpos.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChevronCombo(const char *label, int *current_item,
|
||||||
|
bool (*items_getter)(void *, int, const char **), void *data,
|
||||||
|
int items_count, const char *description)
|
||||||
|
{
|
||||||
|
bool value_changed = false;
|
||||||
|
float combo_width = ImGui::GetColumnWidth();
|
||||||
|
if (*label != '#') {
|
||||||
|
float combo_size_ratio = 0.4;
|
||||||
|
PrepareComboTitleDescription(label, description, combo_size_ratio);
|
||||||
|
combo_width *= combo_size_ratio;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiContext& g = *GImGui;
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(1, 0));
|
||||||
|
|
||||||
|
// Call the getter to obtain the preview string which is a parameter to BeginCombo()
|
||||||
|
const char* preview_value = NULL;
|
||||||
|
if (*current_item >= 0 && *current_item < items_count)
|
||||||
|
items_getter(data, *current_item, &preview_value);
|
||||||
|
|
||||||
|
ImGui::SetNextItemWidth(combo_width);
|
||||||
|
ImGui::PushFont(g_font_mgr.m_menu_font_small);
|
||||||
|
ImGui::PushID(label);
|
||||||
|
if (ImGui::BeginCombo("###chevron_combo", preview_value, ImGuiComboFlags_NoArrowButton)) {
|
||||||
|
// Display items
|
||||||
|
// FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
|
||||||
|
for (int i = 0; i < items_count; i++)
|
||||||
|
{
|
||||||
|
ImGui::PushID(i);
|
||||||
|
const bool item_selected = (i == *current_item);
|
||||||
|
const char* item_text;
|
||||||
|
if (!items_getter(data, i, &item_text))
|
||||||
|
item_text = "*Unknown item*";
|
||||||
|
if (ImGui::Selectable(item_text, item_selected))
|
||||||
|
{
|
||||||
|
value_changed = true;
|
||||||
|
*current_item = i;
|
||||||
|
}
|
||||||
|
if (item_selected)
|
||||||
|
ImGui::SetItemDefaultFocus();
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndCombo();
|
||||||
|
|
||||||
|
if (value_changed)
|
||||||
|
ImGui::MarkItemEdited(g.LastItemData.ID);
|
||||||
|
}
|
||||||
|
ImGui::PopID();
|
||||||
|
ImGui::PopFont();
|
||||||
|
DrawComboChevron();
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
return value_changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter for the old Combo() API: "item1\0item2\0item3\0"
|
||||||
|
static bool Items_SingleStringGetter(void* data, int idx, const char** out_text)
|
||||||
|
{
|
||||||
|
// FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited.
|
||||||
|
const char* items_separated_by_zeros = (const char*)data;
|
||||||
|
int items_count = 0;
|
||||||
|
const char* p = items_separated_by_zeros;
|
||||||
|
while (*p)
|
||||||
|
{
|
||||||
|
if (idx == items_count)
|
||||||
|
break;
|
||||||
|
p += strlen(p) + 1;
|
||||||
|
items_count++;
|
||||||
|
}
|
||||||
|
if (!*p)
|
||||||
|
return false;
|
||||||
|
if (out_text)
|
||||||
|
*out_text = p;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0"
|
||||||
|
bool ChevronCombo(const char* label, int* current_item, const char* items_separated_by_zeros, const char *description)
|
||||||
|
{
|
||||||
|
int items_count = 0;
|
||||||
|
const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open
|
||||||
|
while (*p)
|
||||||
|
{
|
||||||
|
p += strlen(p) + 1;
|
||||||
|
items_count++;
|
||||||
|
}
|
||||||
|
bool value_changed = ChevronCombo(
|
||||||
|
label, current_item, Items_SingleStringGetter,
|
||||||
|
(void *)items_separated_by_zeros, items_count, description);
|
||||||
|
return value_changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Hyperlink(const char *text, const char *url)
|
||||||
|
{
|
||||||
|
ImColor col;
|
||||||
|
ImGui::Text("%s", text);
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
col = IM_COL32_WHITE;
|
||||||
|
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||||
|
} else {
|
||||||
|
col = ImColor(127, 127, 127, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec2 max = ImGui::GetItemRectMax();
|
||||||
|
ImVec2 min = ImGui::GetItemRectMin();
|
||||||
|
min.x -= 1 * g_viewport_mgr.m_scale;
|
||||||
|
min.y = max.y;
|
||||||
|
max.x -= 1 * g_viewport_mgr.m_scale;
|
||||||
|
ImGui::GetWindowDrawList()->AddLine(min, max, col, 1.0 * g_viewport_mgr.m_scale);
|
||||||
|
|
||||||
|
if (ImGui::IsItemClicked()) {
|
||||||
|
xemu_open_web_browser(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HelpMarker(const char* desc)
|
||||||
|
{
|
||||||
|
ImGui::TextDisabled("(?)");
|
||||||
|
if (ImGui::IsItemHovered())
|
||||||
|
{
|
||||||
|
ImGui::BeginTooltip();
|
||||||
|
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
|
||||||
|
ImGui::TextUnformatted(desc);
|
||||||
|
ImGui::PopTextWrapPos();
|
||||||
|
ImGui::EndTooltip();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
//
|
||||||
|
// xemu User Interface
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
#include "common.hh"
|
||||||
|
|
||||||
|
void Separator();
|
||||||
|
void SectionTitle(const char *title);
|
||||||
|
float GetWidgetTitleDescriptionHeight(const char *title,
|
||||||
|
const char *description);
|
||||||
|
void WidgetTitleDescription(const char *title, const char *description,
|
||||||
|
ImVec2 pos);
|
||||||
|
void WidgetTitleDescriptionItem(const char *str_id,
|
||||||
|
const char *description = nullptr);
|
||||||
|
float GetSliderRadius(ImVec2 size);
|
||||||
|
float GetSliderTrackXOffset(ImVec2 size);
|
||||||
|
float GetSliderTrackWidth(ImVec2 size);
|
||||||
|
float GetSliderValueForMousePos(ImVec2 mouse, ImVec2 pos, ImVec2 size);
|
||||||
|
void DrawSlider(float v, bool hovered, ImVec2 pos, ImVec2 size);
|
||||||
|
void DrawToggle(bool enabled, bool hovered, ImVec2 pos, ImVec2 size);
|
||||||
|
bool Toggle(const char *str_id, bool *v, const char *description = nullptr);
|
||||||
|
void Slider(const char *str_id, float *v, const char *description = nullptr);
|
||||||
|
bool FilePicker(const char *str_id, const char **buf, const char *filters,
|
||||||
|
bool dir = false);
|
||||||
|
void DrawComboChevron();
|
||||||
|
void PrepareComboTitleDescription(const char *label, const char *description,
|
||||||
|
float combo_size_ratio);
|
||||||
|
bool ChevronCombo(const char *label, int *current_item,
|
||||||
|
bool (*items_getter)(void *, int, const char **), void *data,
|
||||||
|
int items_count, const char *description = NULL);
|
||||||
|
bool ChevronCombo(const char* label, int* current_item, const char* items_separated_by_zeros, const char *description = NULL);
|
||||||
|
void Hyperlink(const char *text, const char *url);
|
||||||
|
void HelpMarker(const char* desc);
|
|
@ -30,7 +30,6 @@ extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Implemented in xemu.c
|
// Implemented in xemu.c
|
||||||
extern int scaling_mode;
|
|
||||||
int xemu_is_fullscreen(void);
|
int xemu_is_fullscreen(void);
|
||||||
void xemu_monitor_init(void);
|
void xemu_monitor_init(void);
|
||||||
void xemu_toggle_fullscreen(void);
|
void xemu_toggle_fullscreen(void);
|
||||||
|
@ -43,6 +42,7 @@ void xemu_hud_cleanup(void);
|
||||||
void xemu_hud_render(void);
|
void xemu_hud_render(void);
|
||||||
void xemu_hud_process_sdl_events(SDL_Event *event);
|
void xemu_hud_process_sdl_events(SDL_Event *event);
|
||||||
void xemu_hud_should_capture_kbd_mouse(int *kbd, int *mouse);
|
void xemu_hud_should_capture_kbd_mouse(int *kbd, int *mouse);
|
||||||
|
void xemu_hud_set_framebuffer_texture(GLuint tex, bool flip);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
Loading…
Reference in New Issue