mirror of
https://github.com/xemu-project/xemu.git
synced 2024-11-23 03:29:43 +00:00
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: '$'
|
||||
IndentCaseLabels: false
|
||||
IndentWidth: 4
|
||||
AccessModifierOffset: -4
|
||||
IndentWrappedFunctionNames: false
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MacroBlockBegin: '.*_BEGIN$' # only PREC_BEGIN ?
|
||||
|
8
.gitmodules
vendored
8
.gitmodules
vendored
@ -64,12 +64,12 @@
|
||||
[submodule "roms/vbootrom"]
|
||||
path = roms/vbootrom
|
||||
url = https://gitlab.com/qemu-project/vbootrom.git
|
||||
[submodule "ui/imgui"]
|
||||
path = ui/imgui
|
||||
[submodule "ui/thirdparty/imgui"]
|
||||
path = ui/thirdparty/imgui
|
||||
url = https://github.com/ocornut/imgui.git
|
||||
ignore = untracked
|
||||
[submodule "ui/implot"]
|
||||
path = ui/implot
|
||||
[submodule "ui/thirdparty/implot"]
|
||||
path = ui/thirdparty/implot
|
||||
url = https://github.com/epezent/implot.git
|
||||
ignore = untracked
|
||||
[submodule "hw/xbox/nv2a/xxHash"]
|
||||
|
5
build.sh
5
build.sh
@ -272,11 +272,6 @@ set -x # Print commands from now on
|
||||
${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
|
||||
|
||||
"${postbuild}" # call post build functions
|
||||
|
@ -6,8 +6,10 @@ general:
|
||||
check:
|
||||
type: bool
|
||||
default: true
|
||||
misc:
|
||||
skip_boot_anim: bool
|
||||
screenshot_dir: string
|
||||
skip_boot_anim: bool
|
||||
# throttle_io: bool
|
||||
last_viewed_menu_index: integer
|
||||
user_token: string
|
||||
|
||||
input:
|
||||
@ -16,6 +18,11 @@ input:
|
||||
port2: string
|
||||
port3: string
|
||||
port4: string
|
||||
gamecontrollerdb_path: string
|
||||
auto_bind:
|
||||
type: bool
|
||||
default: true
|
||||
background_input_capture: bool
|
||||
|
||||
display:
|
||||
quality:
|
||||
@ -23,9 +30,27 @@ display:
|
||||
type: integer
|
||||
default: 1
|
||||
window:
|
||||
last_width: integer
|
||||
last_height: integer
|
||||
fullscreen_on_startup: bool
|
||||
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:
|
||||
show_menubar:
|
||||
type: bool
|
||||
default: true
|
||||
use_animations:
|
||||
type: bool
|
||||
default: true
|
||||
fit:
|
||||
type: enum
|
||||
values: [center, scale, scale_16_9, scale_4_3, stretch]
|
||||
@ -33,9 +58,15 @@ display:
|
||||
scale:
|
||||
type: integer
|
||||
default: 1
|
||||
auto_scale:
|
||||
type: bool
|
||||
default: true
|
||||
|
||||
audio:
|
||||
use_dsp: bool
|
||||
volume_limit:
|
||||
type: number
|
||||
default: 1
|
||||
|
||||
net:
|
||||
enable: bool
|
||||
@ -52,12 +83,26 @@ net:
|
||||
remote_addr:
|
||||
type: string
|
||||
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:
|
||||
mem_limit:
|
||||
type: enum
|
||||
values: ['64', '128']
|
||||
default: '64'
|
||||
avpack:
|
||||
type: enum
|
||||
values: [scart, hdtv, vga, rfu, svideo, composite, none]
|
||||
default: hdtv
|
||||
files:
|
||||
bootrom_path: string
|
||||
flashrom_path: string
|
||||
@ -69,3 +114,6 @@ perf:
|
||||
hard_fpu:
|
||||
type: bool
|
||||
default: true
|
||||
# cache_shaders:
|
||||
# type: bool
|
||||
# default: true
|
||||
|
2
configure
vendored
2
configure
vendored
@ -261,7 +261,7 @@ else
|
||||
git_submodules_action="ignore"
|
||||
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"
|
||||
|
||||
# Don't accept a target_list environment variable.
|
||||
|
Binary file not shown.
BIN
data/RobotoCondensed-Regular.ttf
Normal file
BIN
data/RobotoCondensed-Regular.ttf
Normal file
Binary file not shown.
156
data/abxy.svg
Normal file
156
data/abxy.svg
Normal file
@ -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 |
BIN
data/abxy.ttf
Normal file
BIN
data/abxy.ttf
Normal file
Binary file not shown.
161
data/abxy_font.svg
Normal file
161
data/abxy_font.svg
Normal file
@ -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 |
BIN
data/font_awesome_6_1_1_solid.otf
Normal file
BIN
data/font_awesome_6_1_1_solid.otf
Normal file
Binary file not shown.
@ -2,7 +2,10 @@ pfiles = [
|
||||
'controller_mask.png',
|
||||
'logo_sdf.png',
|
||||
'xemu_64x64.png',
|
||||
'roboto_medium.ttf',
|
||||
'abxy.ttf',
|
||||
'Roboto-Medium.ttf',
|
||||
'RobotoCondensed-Regular.ttf',
|
||||
'font_awesome_6_1_1_solid.otf',
|
||||
]
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
mcpx_apu_update_dsp_preference(d);
|
||||
mcpx_debug_begin_frame();
|
||||
g_dbg.gp_realtime = d->gp.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][1] += isamp[2*i+1];
|
||||
}
|
||||
|
||||
memset(d->vp.sample_buf, 0, sizeof(d->vp.sample_buf));
|
||||
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);
|
||||
fclose(fd);
|
||||
#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);
|
||||
int num_bytes_free = fifo8_num_free(&d->vp.out_buf);
|
||||
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
|
||||
* use the full audio pipeline or not.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
mcpx_apu_update_dsp_preference(d);
|
||||
|
||||
qemu_thread_create(&d->apu_thread, "mcpx.apu_thread", mcpx_apu_frame_thread,
|
||||
d, QEMU_THREAD_JOINABLE);
|
||||
|
165
licenses/fontawesome.license.txt
Normal file
165
licenses/fontawesome.license.txt
Normal file
@ -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.**
|
24
licenses/fpng.license.txt
Normal file
24
licenses/fpng.license.txt
Normal file
@ -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/>
|
16
licenses/tomlplusplus.license.txt
Normal file
16
licenses/tomlplusplus.license.txt
Normal file
@ -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)
|
||||
{
|
||||
struct in_addr host_addr = { .s_addr = INADDR_ANY };
|
||||
|
@ -28,7 +28,7 @@ sub_file="${sub_tdir}/submodule.tar"
|
||||
# different to the host OS.
|
||||
submodules="dtc slirp meson ui/keycodemapdb"
|
||||
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=""
|
||||
|
||||
function cleanup() {
|
||||
|
@ -20,6 +20,7 @@ bsd_3clause = 'bsd-3clause'
|
||||
zlib = 'zlib'
|
||||
lgplv2_1 = 'lgplv2_1'
|
||||
apache2 = 'apache2'
|
||||
unlicense = 'unlicense'
|
||||
multi = 'multi'
|
||||
|
||||
|
||||
@ -179,13 +180,13 @@ Lib('slirp', 'https://gitlab.freedesktop.org/slirp',
|
||||
Lib('imgui', 'https://github.com/ocornut/imgui',
|
||||
mit, 'https://raw.githubusercontent.com/ocornut/imgui/master/LICENSE.txt',
|
||||
ships_static=all_platforms,
|
||||
submodule=Submodule('ui/imgui')
|
||||
submodule=Submodule('ui/thirdparty/imgui')
|
||||
),
|
||||
|
||||
Lib('implot', 'https://github.com/epezent/implot',
|
||||
mit, 'https://raw.githubusercontent.com/epezent/implot/master/LICENSE',
|
||||
ships_static=all_platforms,
|
||||
submodule=Submodule('ui/implot')
|
||||
submodule=Submodule('ui/thirdparty/implot')
|
||||
),
|
||||
|
||||
Lib('httplib', 'https://github.com/yhirose/cpp-httplib',
|
||||
@ -206,10 +207,10 @@ Lib('stb_image', 'https://github.com/nothings/stb',
|
||||
version='2.25'
|
||||
),
|
||||
|
||||
Lib('inih', 'https://github.com/benhoyt/inih',
|
||||
bsd, 'https://raw.githubusercontent.com/mborgerson/xemu/master/ui/inih/LICENSE.txt',
|
||||
Lib('tomlplusplus', 'https://github.com/marzer/tomlplusplus',
|
||||
mit, 'https://raw.githubusercontent.com/marzer/tomlplusplus/master/LICENSE',
|
||||
ships_static=all_platforms,
|
||||
version='351217124ddb3e3fe2b982248a04c672350bb0af'
|
||||
submodule=Submodule('tomlplusplus')
|
||||
),
|
||||
|
||||
Lib('xxHash', 'https://github.com/Cyan4973/xxHash.git',
|
||||
@ -218,6 +219,12 @@ Lib('xxHash', 'https://github.com/Cyan4973/xxHash.git',
|
||||
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
|
||||
#
|
||||
@ -228,6 +235,12 @@ Lib('roboto', 'https://github.com/googlefonts/roboto',
|
||||
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
|
||||
#
|
||||
|
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 : "",
|
||||
g_config.general.misc.skip_boot_anim ? ",short-animation=on" : "",
|
||||
",kernel-irqchip=off"
|
||||
g_config.general.skip_boot_anim ? ",short-animation=on" : "",
|
||||
",kernel-irqchip=off",
|
||||
avpack_str
|
||||
);
|
||||
|
||||
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',
|
||||
))
|
||||
|
||||
imgui_files = files(
|
||||
'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')
|
||||
subdir('thirdparty')
|
||||
|
||||
xemu_ss = ss.source_set()
|
||||
xemu_ss.add(files(
|
||||
'xemu.c',
|
||||
'xemu-custom-widgets.c',
|
||||
'xemu-data.c',
|
||||
'xemu-input.c',
|
||||
'xemu-monitor.c',
|
||||
'xemu-net.c',
|
||||
'xemu-settings.cc',
|
||||
'xemu-shaders.c',
|
||||
'xemu-hud.cc',
|
||||
'xemu-reporting.cc',
|
||||
|
||||
'xemu.c',
|
||||
'xemu-data.c',
|
||||
))
|
||||
|
||||
xemu_ss.add(when: 'CONFIG_WIN32', if_true: files('xemu-update.cc'))
|
||||
subdir('xui')
|
||||
|
||||
if 'CONFIG_DARWIN' in config_host
|
||||
xemu_cocoa = dependency('appleframeworks', modules: 'Cocoa')
|
||||
xemu_ss.add(xemu_cocoa) # FIXME: Use existing cocoa name
|
||||
xemu_ss.add(xemu_cocoa)
|
||||
endif
|
||||
|
||||
xemu_ss.add(imgui, sdl, opengl, openssl)
|
||||
xemu_ss.add(when: 'CONFIG_LINUX', if_true: [xemu_gtk, files('xemu-os-utils-linux.c', 'noc_file_dialog_gtk.c')])
|
||||
xemu_ss.add(when: 'CONFIG_WIN32', if_true: files('xemu-os-utils-windows.c', 'noc_file_dialog_win32.c'))
|
||||
xemu_ss.add(when: 'CONFIG_DARWIN', if_true: files('xemu-os-utils-macos.m', 'noc_file_dialog_macos.m'))
|
||||
if 'CONFIG_LINUX' in config_host
|
||||
xemu_ss.add(xemu_gtk)
|
||||
endif
|
||||
|
||||
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(imgui, implot, stb_image, noc, sdl, opengl, openssl, fa, fpng, json, httplib)
|
||||
|
||||
softmmu_ss.add_all(xemu_ss)
|
||||
|
||||
|
1393
ui/thirdparty/fa/IconsFontAwesome6.h
vendored
Normal file
1393
ui/thirdparty/fa/IconsFontAwesome6.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
ui/thirdparty/fpng/HEAD
vendored
Normal file
1
ui/thirdparty/fpng/HEAD
vendored
Normal file
@ -0,0 +1 @@
|
||||
645d49cf6b2e82ce25b5b59f6a2e2df30e6f5fa6
|
3222
ui/thirdparty/fpng/fpng.cpp
vendored
Normal file
3222
ui/thirdparty/fpng/fpng.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
122
ui/thirdparty/fpng/fpng.h
vendored
Normal file
122
ui/thirdparty/fpng/fpng.h
vendored
Normal file
@ -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
|
1
ui/thirdparty/imgui
vendored
Submodule
1
ui/thirdparty/imgui
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit c71a50deb5ddf1ea386b91e60fa2e4a26d080074
|
1
ui/thirdparty/imgui_impl_opengl3_loader_override.h
vendored
Normal file
1
ui/thirdparty/imgui_impl_opengl3_loader_override.h
vendored
Normal file
@ -0,0 +1 @@
|
||||
#include <epoxy/gl.h>
|
1
ui/thirdparty/implot
vendored
Submodule
1
ui/thirdparty/implot
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit b47c8bacdbc78bc521691f70666f13924bb522ab
|
63
ui/thirdparty/meson.build
vendored
Normal file
63
ui/thirdparty/meson.build
vendored
Normal file
@ -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
|
||||
* 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
|
||||
* windows.
|
||||
@ -328,3 +330,4 @@ const char *noc_file_dialog_open(int flags,
|
||||
|
||||
|
||||
#endif
|
||||
#endif
|
475
ui/stb_image.h → ui/thirdparty/stb_image/stb_image.h
vendored
475
ui/stb_image.h → ui/thirdparty/stb_image/stb_image.h
vendored
File diff suppressed because it is too large
Load Diff
2
ui/thirdparty/stb_image/stb_image_impl.c
vendored
Normal file
2
ui/thirdparty/stb_image/stb_image_impl.c
vendored
Normal file
@ -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)
|
||||
{
|
||||
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) {
|
||||
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
|
||||
// ports and just needs to bind to the receiver and never needs to hit
|
||||
// this dialog.
|
||||
|
||||
|
||||
// Attempt to re-bind to port previously bound to
|
||||
int port = 0;
|
||||
while (1) {
|
||||
bool did_bind = false;
|
||||
while (!did_bind) {
|
||||
port = xemu_input_get_controller_default_bind_port(new_con, port);
|
||||
if (port < 0) {
|
||||
// No (additional) default mappings
|
||||
break;
|
||||
}
|
||||
if (xemu_input_get_bound(port) != NULL) {
|
||||
// Something already bound here, try again for another port
|
||||
} else if (!xemu_input_get_bound(port)) {
|
||||
xemu_input_bind(port, new_con, 0);
|
||||
did_bind = true;
|
||||
break;
|
||||
} else {
|
||||
// Try again for another 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];
|
||||
snprintf(buf, sizeof(buf), "Connected '%s' to port %d", new_con->name, port+1);
|
||||
xemu_queue_notification(buf);
|
||||
break;
|
||||
}
|
||||
|
||||
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||
DPRINTF("Controller Removed: %d\n", event->cdevice.which);
|
||||
int handled = 0;
|
||||
|
@ -33,6 +33,8 @@
|
||||
#include "qemu/config-file.h"
|
||||
#include "net/net.h"
|
||||
#include "net/hub.h"
|
||||
#include "net/slirp.h"
|
||||
#include <libslirp.h>
|
||||
#if defined(_WIN32)
|
||||
#include <pcap/pcap.h>
|
||||
#endif
|
||||
@ -41,6 +43,8 @@
|
||||
static const char *id = "xemu-netdev";
|
||||
static const char *id_hubport = "xemu-netdev-hubport";
|
||||
|
||||
void *slirp_get_state_from_netdev(const char *id);
|
||||
|
||||
void xemu_net_enable(void)
|
||||
{
|
||||
Error *local_err = NULL;
|
||||
@ -102,6 +106,38 @@ void xemu_net_enable(void)
|
||||
// error_propagate(errp, local_err);
|
||||
xemu_queue_error_message(error_get_pretty(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;
|
||||
@ -124,13 +160,25 @@ static void remove_netdev(const char *name)
|
||||
// error_setg(errp, "Device '%s' is not a netdev", name);
|
||||
return;
|
||||
}
|
||||
|
||||
qemu_opts_del(opts);
|
||||
qemu_del_net_client(nc);
|
||||
}
|
||||
|
||||
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_hubport);
|
||||
g_config.net.enable = false;
|
||||
|
@ -25,9 +25,46 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
const char *xemu_get_os_info(void);
|
||||
const char *xemu_get_cpu_info(void);
|
||||
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
|
||||
}
|
||||
#endif
|
||||
|
@ -187,3 +187,31 @@ void xemu_settings_save(void)
|
||||
fprintf(fd, "%s", config_tree.generate_delta_toml().c_str());
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
#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-action.h"
|
||||
#include "sysemu/sysemu.h"
|
||||
#include "xemu-hud.h"
|
||||
#include "xui/xemu-hud.h"
|
||||
#include "xemu-input.h"
|
||||
#include "xemu-settings.h"
|
||||
#include "xemu-shaders.h"
|
||||
// #include "xemu-shaders.h"
|
||||
#include "xemu-version.h"
|
||||
#include "xemu-os-utils.h"
|
||||
|
||||
@ -55,6 +55,8 @@
|
||||
#include "hw/xbox/smbus.h" // For eject, drive tray
|
||||
#include "hw/xbox/nv2a/nv2a.h"
|
||||
|
||||
#include <stb_image.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
// Provide hint to prefer high-performance graphics for hybrid systems
|
||||
// https://gpuopen.com/learn/amdpowerxpressrequesthighperformance/
|
||||
@ -108,7 +110,7 @@ static SDL_Cursor *guest_sprite;
|
||||
static Notifier mouse_mode_notifier;
|
||||
static SDL_Window *m_window;
|
||||
static SDL_GLContext m_context;
|
||||
struct decal_shader *blit;
|
||||
// struct decal_shader *blit;
|
||||
|
||||
static QemuSemaphore display_init_sem;
|
||||
|
||||
@ -845,28 +847,58 @@ static void sdl2_display_very_early_init(DisplayOptions *o)
|
||||
#endif
|
||||
, 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
|
||||
m_window = SDL_CreateWindow(
|
||||
title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480,
|
||||
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||
title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, window_width, window_height,
|
||||
window_flags);
|
||||
if (m_window == NULL) {
|
||||
fprintf(stderr, "Failed to create main window\n");
|
||||
SDL_Quit();
|
||||
exit(1);
|
||||
}
|
||||
g_free(title);
|
||||
SDL_SetWindowMinimumSize(m_window, min_window_width, min_window_height);
|
||||
|
||||
SDL_DisplayMode disp_mode;
|
||||
SDL_GetCurrentDisplayMode(SDL_GetWindowDisplayIndex(m_window), &disp_mode);
|
||||
|
||||
int win_w = g_config.display.window.last_width,
|
||||
win_h = g_config.display.window.last_height;
|
||||
|
||||
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);
|
||||
}
|
||||
if (disp_mode.w < window_width || disp_mode.h < window_height) {
|
||||
SDL_SetWindowSize(m_window, min_window_width, min_window_height);
|
||||
SDL_SetWindowPosition(m_window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||
}
|
||||
|
||||
m_context = SDL_GL_CreateContext(m_window);
|
||||
@ -923,9 +955,9 @@ static void sdl2_display_early_init(DisplayOptions *o)
|
||||
display_opengl = 1;
|
||||
|
||||
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);
|
||||
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)
|
||||
@ -942,6 +974,8 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
|
||||
|
||||
gui_fullscreen = o->has_full_screen && o->full_screen;
|
||||
|
||||
gui_fullscreen |= g_config.display.window.fullscreen_on_startup;
|
||||
|
||||
#if 1
|
||||
// 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
|
||||
@ -1145,6 +1179,7 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl)
|
||||
*/
|
||||
GLuint tex = nv2a_get_framebuffer_surface();
|
||||
if (tex == 0) {
|
||||
// FIXME: Don't upload if notdirty
|
||||
xb_surface_gl_create_texture(scon->surface);
|
||||
scon->updates++;
|
||||
tex = scon->surface->texture;
|
||||
@ -1160,72 +1195,9 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl)
|
||||
qemu_mutex_lock_iothread();
|
||||
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);
|
||||
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();
|
||||
|
||||
// Release BQL before swapping (which may sleep if swap interval is not immediate)
|
||||
|
65
ui/xui/actions.cc
Normal file
65
ui/xui/actions.cc
Normal file
@ -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;
|
||||
}
|
26
ui/xui/actions.hh
Normal file
26
ui/xui/actions.hh
Normal file
@ -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();
|
161
ui/xui/animation.cc
Normal file
161
ui/xui/animation.cc
Normal file
@ -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;
|
||||
}
|
73
ui/xui/animation.hh
Normal file
73
ui/xui/animation.hh
Normal file
@ -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();
|
||||
};
|
55
ui/xui/common.hh
Normal file
55
ui/xui/common.hh
Normal file
@ -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;
|
220
ui/xui/compat.cc
Normal file
220
ui/xui/compat.cc
Normal file
@ -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;
|
41
ui/xui/compat.hh
Normal file
41
ui/xui/compat.hh
Normal file
@ -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;
|
323
ui/xui/debug.cc
Normal file
323
ui/xui/debug.cc
Normal file
@ -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;
|
40
ui/xui/debug.hh
Normal file
40
ui/xui/debug.hh
Normal file
@ -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;
|
118
ui/xui/font-manager.cc
Normal file
118
ui/xui/font-manager.cc
Normal file
@ -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;
|
||||
}
|
||||
}
|
45
ui/xui/font-manager.hh
Normal file
45
ui/xui/font-manager.hh
Normal file
@ -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;
|
777
ui/xui/gl-helpers.cc
Normal file
777
ui/xui/gl-helpers.cc
Normal file
@ -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);
|
||||
}
|
||||
}
|
50
ui/xui/gl-helpers.hh
Normal file
50
ui/xui/gl-helpers.hh
Normal file
@ -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);
|
116
ui/xui/input-manager.cc
Normal file
116
ui/xui/input-manager.cc
Normal file
@ -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
|
||||
}
|
20
ui/xui/input-manager.hh
Normal file
20
ui/xui/input-manager.hh
Normal file
@ -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;
|
1142
ui/xui/main-menu.cc
Normal file
1142
ui/xui/main-menu.cc
Normal file
File diff suppressed because it is too large
Load Diff
187
ui/xui/main-menu.hh
Normal file
187
ui/xui/main-menu.hh
Normal file
@ -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;
|
306
ui/xui/main.cc
Normal file
306
ui/xui/main.cc
Normal file
@ -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;
|
||||
}
|
||||
}
|
192
ui/xui/menubar.cc
Normal file
192
ui/xui/menubar.cc
Normal file
@ -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();
|
||||
}
|
||||
}
|
22
ui/xui/menubar.hh
Normal file
22
ui/xui/menubar.hh
Normal file
@ -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();
|
24
ui/xui/meson.build
Normal file
24
ui/xui/meson.build
Normal file
@ -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'))
|
106
ui/xui/misc.hh
Normal file
106
ui/xui/misc.hh
Normal file
@ -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;
|
||||
}
|
155
ui/xui/monitor.cc
Normal file
155
ui/xui/monitor.cc
Normal file
@ -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;
|
50
ui/xui/monitor.hh
Normal file
50
ui/xui/monitor.hh
Normal file
@ -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;
|
155
ui/xui/notifications.cc
Normal file
155
ui/xui/notifications.cc
Normal file
@ -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);
|
||||
}
|
46
ui/xui/notifications.hh
Normal file
46
ui/xui/notifications.hh
Normal file
@ -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;
|
511
ui/xui/popup-menu.cc
Normal file
511
ui/xui/popup-menu.cc
Normal file
@ -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;
|
85
ui/xui/popup-menu.hh
Normal file
85
ui/xui/popup-menu.hh
Normal file
@ -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
|
||||
*
|
||||
* Title compatibility and bug report submission.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
//
|
||||
// xemu Reporting
|
||||
//
|
||||
// Title compatibility and bug report submission.
|
||||
//
|
||||
// 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 <glib.h>
|
||||
#include <glib/gi18n.h>
|
||||
#include <stdio.h>
|
||||
#include "xemu-reporting.h"
|
||||
#include "reporting.hh"
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT 1
|
||||
#include "httplib.h"
|
||||
#include "json.hpp"
|
||||
#include <httplib.h>
|
||||
#include <json.hpp>
|
||||
using json = nlohmann::json;
|
||||
|
||||
#define DEBUG_COMPAT_SERVICE 0
|
@ -1,26 +1,23 @@
|
||||
/*
|
||||
* xemu Reporting
|
||||
*
|
||||
* Title compatibility and bug report submission.
|
||||
*
|
||||
* 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_REPORTING_H
|
||||
#define XEMU_REPORTING_H
|
||||
//
|
||||
// xemu Reporting
|
||||
//
|
||||
// Title compatibility and bug report submission.
|
||||
//
|
||||
// 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/>.
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
@ -59,5 +56,3 @@ public:
|
||||
const std::string &GetSerializedReport();
|
||||
void SetXbeData(struct xbe *xbe);
|
||||
};
|
||||
|
||||
#endif
|
278
ui/xui/scene-components.cc
Normal file
278
ui/xui/scene-components.cc
Normal file
@ -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
|
91
ui/xui/scene-components.hh
Normal file
91
ui/xui/scene-components.hh
Normal file
@ -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
|
52
ui/xui/scene-manager.cc
Normal file
52
ui/xui/scene-manager.cc
Normal file
@ -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;
|
||||
}
|
36
ui/xui/scene-manager.hh
Normal file
36
ui/xui/scene-manager.hh
Normal file
@ -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;
|
25
ui/xui/scene.cc
Normal file
25
ui/xui/scene.cc
Normal file
@ -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; }
|
30
ui/xui/scene.hh
Normal file
30
ui/xui/scene.hh
Normal file
@ -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();
|
||||
};
|
276
ui/xui/update.cc
Normal file
276
ui/xui/update.cc
Normal file
@ -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);
|
||||
}
|
92
ui/xui/update.hh
Normal file
92
ui/xui/update.hh
Normal file
@ -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
|
89
ui/xui/viewport-manager.cc
Normal file
89
ui/xui/viewport-manager.cc
Normal file
@ -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);
|
||||
}
|
||||
}
|
36
ui/xui/viewport-manager.hh
Normal file
36
ui/xui/viewport-manager.hh
Normal file
@ -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;
|
99
ui/xui/welcome.cc
Normal file
99
ui/xui/welcome.cc
Normal file
@ -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;
|
29
ui/xui/welcome.hh
Normal file
29
ui/xui/welcome.hh
Normal file
@ -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;
|
506
ui/xui/widgets.cc
Normal file
506
ui/xui/widgets.cc
Normal file
@ -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();
|
||||
}
|
||||
}
|
48
ui/xui/widgets.hh
Normal file
48
ui/xui/widgets.hh
Normal file
@ -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
|
||||
|
||||
// Implemented in xemu.c
|
||||
extern int scaling_mode;
|
||||
int xemu_is_fullscreen(void);
|
||||
void xemu_monitor_init(void);
|
||||
void xemu_toggle_fullscreen(void);
|
||||
@ -43,6 +42,7 @@ void xemu_hud_cleanup(void);
|
||||
void xemu_hud_render(void);
|
||||
void xemu_hud_process_sdl_events(SDL_Event *event);
|
||||
void xemu_hud_should_capture_kbd_mouse(int *kbd, int *mouse);
|
||||
void xemu_hud_set_framebuffer_texture(GLuint tex, bool flip);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
Loading…
Reference in New Issue
Block a user