mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-08 17:28:49 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adae0656eb | ||
|
|
2c587731bd | ||
|
|
4d231d8c27 | ||
|
|
266f7a3ad9 | ||
|
|
668ab0f0c4 | ||
|
|
2a0e34d66e | ||
|
|
e517e9b96a | ||
|
|
99aed1723f | ||
|
|
ad542822ed | ||
|
|
1b4d622bb4 | ||
|
|
9b71cdbb0f | ||
|
|
02381aabef | ||
|
|
790cf1c27e | ||
|
|
507379406d | ||
|
|
82913126ef | ||
|
|
432e5c02de | ||
|
|
8f93a6f379 | ||
|
|
106ae968a0 | ||
|
|
2008327742 | ||
|
|
2a8753d13c | ||
|
|
afa01c5421 | ||
|
|
4f4ef6e815 | ||
|
|
6f637df360 | ||
|
|
b64fe2adc2 | ||
|
|
1b8eb669d8 | ||
|
|
84e0251a86 | ||
|
|
a22062f4f9 | ||
|
|
32525ea0d3 | ||
|
|
d560660bdc | ||
|
|
f7f613a244 | ||
|
|
fcbcfab3d9 | ||
|
|
c2882164f9 | ||
|
|
c101b4ccb3 | ||
|
|
d889f594a3 | ||
|
|
e09075dc25 | ||
|
|
b8bbf77196 | ||
|
|
6804540635 | ||
|
|
08621cbd20 | ||
|
|
b35fdee119 | ||
|
|
826a3eff46 | ||
|
|
dcd71b403f | ||
|
|
cfc7f4c8c7 | ||
|
|
58233137b0 | ||
|
|
70e85ad719 | ||
|
|
f5379815da | ||
|
|
2a4c857df9 | ||
|
|
df09b0dc9b | ||
|
|
a02cade1f2 | ||
|
|
12ae59a197 | ||
|
|
b9036e26a7 | ||
|
|
093c1c5a12 | ||
|
|
e66c2d937a | ||
|
|
d3d9229f1d | ||
|
|
43c42c28e6 | ||
|
|
69c05cd7b4 | ||
|
|
c1ce224bd7 | ||
|
|
b14593081d | ||
|
|
6ea0eae4bb | ||
|
|
4b29c9dcad | ||
|
|
15be2d6b34 | ||
|
|
9fe27117f2 | ||
|
|
75bdde358a | ||
|
|
8a3567ab61 | ||
|
|
058dc172c7 | ||
|
|
6bf26dc420 | ||
|
|
1cf6f2f20f | ||
|
|
bcaa24cf7b | ||
|
|
ecfb0dda43 | ||
|
|
3c4f13e9f5 | ||
|
|
2211f4391c | ||
|
|
730f8d8996 | ||
|
|
aa99f4f5ba | ||
|
|
d2cdc97e8f | ||
|
|
b75bb07329 | ||
|
|
63d36f658d | ||
|
|
9b1ab23aa1 | ||
|
|
c800477bc8 | ||
|
|
28ef2d6487 | ||
|
|
56e63d4460 | ||
|
|
2444d8b39c | ||
|
|
bcb97caa4e | ||
|
|
a7e19107ce | ||
|
|
5733d8ebd5 | ||
|
|
3a590f126d | ||
|
|
434bec8775 | ||
|
|
913eab54ff | ||
|
|
c3650f077a | ||
|
|
e067dac2a4 | ||
|
|
a62052eb0a | ||
|
|
abd22ae770 | ||
|
|
5e2e586f6e | ||
|
|
26711ec0d5 | ||
|
|
6ad1a5cc8f | ||
|
|
e73a7c24e2 | ||
|
|
e247867bd7 | ||
|
|
dc521b42d1 | ||
|
|
525c8f7c8e | ||
|
|
633cadcde7 | ||
|
|
912068f850 | ||
|
|
2ce1dbcf02 | ||
|
|
3a141cb891 | ||
|
|
2fda0b7e66 | ||
|
|
0dc0cc5bf0 | ||
|
|
b4902e7df5 | ||
|
|
95495cbf62 | ||
|
|
87c94982c2 | ||
|
|
e5d930ae0a | ||
|
|
ff3a0d9a3c | ||
|
|
2aa004a1ce | ||
|
|
344a4099eb | ||
|
|
c2a4fa83e8 | ||
|
|
15b1bb40a4 | ||
|
|
e2d8771baf | ||
|
|
790fcd2264 | ||
|
|
c576faae0c | ||
|
|
760fee18dd | ||
|
|
1521a8dd38 | ||
|
|
c9b88fa1ab | ||
|
|
708596ba84 | ||
|
|
d27252daed | ||
|
|
9d91a38602 | ||
|
|
22dee03c65 | ||
|
|
4eb0f21b8f | ||
|
|
21cb98e3fb | ||
|
|
8a618f8e95 | ||
|
|
1d2b5fb534 | ||
|
|
f6ca13b658 | ||
|
|
f8397a17c3 | ||
|
|
49753a7385 | ||
|
|
abba2f387e | ||
|
|
39b1e43cbc | ||
|
|
aed74a008c | ||
|
|
2a6ffd9c3b | ||
|
|
ffc393cafc | ||
|
|
260f9532e9 | ||
|
|
22eb2f8f3b | ||
|
|
933627353b | ||
|
|
5ac0141a9a | ||
|
|
d1dc2e9670 | ||
|
|
3c07209a1a | ||
|
|
e9d8525e29 | ||
|
|
b4b3887a1a | ||
|
|
58de712b77 | ||
|
|
fb33dcf17b | ||
|
|
edc64991f3 | ||
|
|
3e2c713893 | ||
|
|
645bae4931 | ||
|
|
60296c0b7d | ||
|
|
6995154a9a | ||
|
|
e9f9702a2d | ||
|
|
529ecf1e36 | ||
|
|
95f4910a94 | ||
|
|
0a7968761f | ||
|
|
4293e5b334 | ||
|
|
5636b0fdd6 | ||
|
|
c9a9880a36 | ||
|
|
fd11190e9d | ||
|
|
fe829e5eb0 | ||
|
|
bec00d986c | ||
|
|
768dbb5210 | ||
|
|
626b8b5dae | ||
|
|
5de9ba0bc2 | ||
|
|
4813019c58 | ||
|
|
0c7f209e65 | ||
|
|
ccd5dec266 | ||
|
|
5ba36ad3a1 | ||
|
|
36f700927e | ||
|
|
fa8db9b4d2 | ||
|
|
39679ccdce | ||
|
|
63d2e543af | ||
|
|
540fdf358c | ||
|
|
25c18bb3ce | ||
|
|
17d699c75b | ||
|
|
8fbcf34e02 | ||
|
|
175aaadb0d | ||
|
|
b4173ee0b4 | ||
|
|
e58eee37bc | ||
|
|
7c41e298a2 | ||
|
|
d561c7ff2d | ||
|
|
431f1b4b7f | ||
|
|
ddb4867c52 | ||
|
|
e1041030a2 | ||
|
|
b11620e1eb | ||
|
|
50b07ad8c4 | ||
|
|
ba71dda1b2 | ||
|
|
be98b0ea0b | ||
|
|
7cda786906 | ||
|
|
03ee897ae2 | ||
|
|
c16bb5ff5e | ||
|
|
c1c5ebf5f6 | ||
|
|
bfe8a8d48a | ||
|
|
2127f5202b | ||
|
|
e3e41e433e | ||
|
|
8b972a8d82 | ||
|
|
94af3addd0 | ||
|
|
f5a12d63cc | ||
|
|
688414c5bb | ||
|
|
d3868cc06f | ||
|
|
20e7ac4280 | ||
|
|
411ade7fb9 | ||
|
|
6bbe74d260 | ||
|
|
ff3a750e71 | ||
|
|
721a8f6d47 | ||
|
|
0ece49df50 | ||
|
|
c444659683 | ||
|
|
84e799fd09 | ||
|
|
e1392b0fd4 | ||
|
|
e34dc17859 | ||
|
|
15ca6c9507 | ||
|
|
56f0bc2dcc | ||
|
|
fc8392d1e7 | ||
|
|
9a382ce6f4 | ||
|
|
8487966c0b | ||
|
|
97063701f8 | ||
|
|
b74b0ceed5 | ||
|
|
5d1cfc47cc | ||
|
|
aaa590c817 | ||
|
|
57514668f3 | ||
|
|
af3b2f22ec | ||
|
|
737adeabe7 | ||
|
|
33c96bf6f2 | ||
|
|
69061352a7 | ||
|
|
38e5d062e8 | ||
|
|
0aa4edafcf | ||
|
|
57c20bdee7 | ||
|
|
b7666b56d1 | ||
|
|
89b345e262 | ||
|
|
b661466516 | ||
|
|
505d45fdc7 | ||
|
|
64cd82ceb2 | ||
|
|
aec2dcb62a | ||
|
|
bc64ad7774 | ||
|
|
02fb4cf718 | ||
|
|
4ad5f67663 | ||
|
|
e7d623f971 | ||
|
|
5c5c5ecbdf | ||
|
|
379546207f | ||
|
|
091eff9879 | ||
|
|
98beb5d64c | ||
|
|
681351c581 | ||
|
|
ffa9ddeec2 | ||
|
|
7b57696a51 | ||
|
|
8068bcccbe | ||
|
|
59bfe30214 | ||
|
|
37c0cf50fd | ||
|
|
fcc3260341 | ||
|
|
736defed49 | ||
|
|
7202d38c57 | ||
|
|
e08089de48 | ||
|
|
d67348b467 | ||
|
|
eca0ac054a | ||
|
|
5dfd97ff29 | ||
|
|
6aa99ebb37 | ||
|
|
bf5a513e2d | ||
|
|
4ae7718f1c | ||
|
|
e8976762a5 | ||
|
|
53f089ff09 | ||
|
|
c2c4eeb4ca | ||
|
|
9bf8465e02 | ||
|
|
852ef54b7d | ||
|
|
c6e6b394d1 | ||
|
|
540b9d5615 | ||
|
|
c2c2e0cdc9 | ||
|
|
652452b905 | ||
|
|
2bc417e29f | ||
|
|
66fc9e6a06 | ||
|
|
6803c16bae | ||
|
|
e2c3f42390 | ||
|
|
4200c2f135 | ||
|
|
f087419165 | ||
|
|
78339ae67c | ||
|
|
b2257347a3 | ||
|
|
541cfbd635 | ||
|
|
93ea78772d | ||
|
|
a1be55f655 | ||
|
|
d5cdbca322 | ||
|
|
1f841909f5 | ||
|
|
f850e64037 | ||
|
|
e23f0be913 | ||
|
|
914acd3956 | ||
|
|
b134c5d23c | ||
|
|
605d877279 | ||
|
|
3d45ba29d8 | ||
|
|
097d9bc13b | ||
|
|
56d744ceaa | ||
|
|
652e00b93e | ||
|
|
e0cc113689 | ||
|
|
eb371741a2 | ||
|
|
a3f216ba86 | ||
|
|
4850af8eac | ||
|
|
c010c5b258 | ||
|
|
5c973b6649 | ||
|
|
2d76e98c22 | ||
|
|
55e23395e0 | ||
|
|
96ded45dd2 | ||
|
|
6db8aa8a65 | ||
|
|
c990a57359 | ||
|
|
bcae2fdc9b | ||
|
|
5267b630f8 | ||
|
|
e6a1afdb22 | ||
|
|
cf71d465a3 | ||
|
|
6af5bd01bf | ||
|
|
7e8aabd080 | ||
|
|
822844e676 | ||
|
|
55e6ddc88e | ||
|
|
8980c8a46b | ||
|
|
d25a749702 | ||
|
|
2492f3d6b3 | ||
|
|
7f5986e1d5 | ||
|
|
6bc0f02435 | ||
|
|
aa45e5023f | ||
|
|
d5a1ad0a73 | ||
|
|
fca0e52bdd | ||
|
|
39ea7deef7 | ||
|
|
2e9c5ba6f9 | ||
|
|
34dd03a16f | ||
|
|
c7ac70a923 | ||
|
|
2496a6287c | ||
|
|
08b6fdb697 | ||
|
|
470995630b | ||
|
|
11d4827c9f | ||
|
|
fba0080ebc | ||
|
|
8d1e51af7c | ||
|
|
1598042e8e | ||
|
|
e610a906b5 | ||
|
|
77cc37743c | ||
|
|
288657971d | ||
|
|
f5a126df24 | ||
|
|
22fc07a839 | ||
|
|
075930c420 | ||
|
|
364440813f | ||
|
|
a41b8bc199 | ||
|
|
e92feb85af | ||
|
|
895c817fa5 | ||
|
|
5e0e970c92 | ||
|
|
483aceb2af | ||
|
|
0a77d35cb9 | ||
|
|
42e56c8432 | ||
|
|
0751cb238d | ||
|
|
060753258a | ||
|
|
68516cf74c | ||
|
|
e2d4ff5614 | ||
|
|
9ec396c0ea | ||
|
|
c14c4cd394 | ||
|
|
5081d03194 | ||
|
|
7e9cb39519 | ||
|
|
612cb54cfe | ||
|
|
80e2911bec | ||
|
|
d482739dfd | ||
|
|
d0aad07930 | ||
|
|
0319041ffb | ||
|
|
95e588ce0f | ||
|
|
9b13894438 | ||
|
|
34be5566dd | ||
|
|
84f5fa1f26 | ||
|
|
c70526b7af | ||
|
|
e8c73c6683 | ||
|
|
d32cf4c866 | ||
|
|
37656eed84 | ||
|
|
cea6dbbb6a | ||
|
|
cffe023085 | ||
|
|
6d85e1f0a6 | ||
|
|
e698e75a51 | ||
|
|
34c1c5a361 | ||
|
|
473aed632d | ||
|
|
33905af090 | ||
|
|
3a34329cb7 | ||
|
|
44fdbf7763 | ||
|
|
96ab060995 | ||
|
|
cdf7a403ea | ||
|
|
cdd7fbeb52 | ||
|
|
8f5f3d15f3 | ||
|
|
5812471350 | ||
|
|
420d0d0607 | ||
|
|
65633c93f3 | ||
|
|
cd3276c554 | ||
|
|
8980f8c1ea | ||
|
|
eb08a86647 | ||
|
|
b633b8ad2d | ||
|
|
39108f97e9 | ||
|
|
44930716ba | ||
|
|
a74197dc49 | ||
|
|
f86f1b3e32 | ||
|
|
aaf3c876eb | ||
|
|
e2be9e659d | ||
|
|
d7ff2de094 | ||
|
|
6b1b8fc35a | ||
|
|
0c3283574d | ||
|
|
ab087faa2a | ||
|
|
5c84f9158e | ||
|
|
a371815efd | ||
|
|
eb5bf9451d | ||
|
|
c711749973 | ||
|
|
4a5593255f | ||
|
|
5ef8b69c9c | ||
|
|
dfcbd77a6b | ||
|
|
afa07219cd | ||
|
|
f9061ca931 | ||
|
|
9a471d0ef4 | ||
|
|
c7036b24d4 | ||
|
|
0cbe5164b4 | ||
|
|
5887c46e08 | ||
|
|
11f8938f7e | ||
|
|
0dd2304e7c | ||
|
|
ceca89e104 | ||
|
|
6c81007eb9 | ||
|
|
803ae53372 | ||
|
|
f6c2d97ed5 | ||
|
|
fb3c35dc9f | ||
|
|
b610d034ae | ||
|
|
34b5d0025b | ||
|
|
486660471e | ||
|
|
d9e6cbb065 | ||
|
|
59c3f8b06e | ||
|
|
603d4d0464 | ||
|
|
9e93bdbe27 | ||
|
|
3dede6d71a | ||
|
|
c3f5c499f3 | ||
|
|
afd6a2bec5 | ||
|
|
604a558382 | ||
|
|
deb804ab2f | ||
|
|
c7b45aaf80 | ||
|
|
3d61c7fdfc | ||
|
|
b148ae8092 | ||
|
|
bf20021338 | ||
|
|
3443153ccc | ||
|
|
96bfb35e0b | ||
|
|
89fe5625cb | ||
|
|
d88b76c26f | ||
|
|
829392398e | ||
|
|
f5225a479e | ||
|
|
d479bd0e1e | ||
|
|
3d54afdc1b | ||
|
|
1d65222051 | ||
|
|
5d78c2209f | ||
|
|
efdfb29d50 | ||
|
|
7685f93d00 | ||
|
|
ded0813f6b | ||
|
|
f307135d1d | ||
|
|
470c857470 | ||
|
|
6b3dbff75f | ||
|
|
0bd998665b | ||
|
|
007bb0de39 | ||
|
|
9ebfb36b4e | ||
|
|
e276142be3 | ||
|
|
4f22c3d13e | ||
|
|
15c4f82dec | ||
|
|
ae98ea7210 | ||
|
|
eee1ff7b46 | ||
|
|
c9d90ce1b6 | ||
|
|
cacde2b464 | ||
|
|
7bcffde6c6 | ||
|
|
f5fc95cd04 | ||
|
|
c3c772a79a | ||
|
|
d903b1cc60 | ||
|
|
304a647248 | ||
|
|
8f55e77afe | ||
|
|
c6c8881ab2 | ||
|
|
37c0a2d923 | ||
|
|
f5c5f489b3 | ||
|
|
dae733132a | ||
|
|
4a8d8d4d9f | ||
|
|
8b945a103f | ||
|
|
a3c88fec74 | ||
|
|
ab793b92a3 | ||
|
|
1911aadc19 | ||
|
|
85058fbe5c | ||
|
|
a8a8bde2ef | ||
|
|
210da4d204 | ||
|
|
05a90f7f70 | ||
|
|
45305b4c04 | ||
|
|
39a9ac8444 | ||
|
|
5efb90d614 | ||
|
|
d0a4632c13 | ||
|
|
97e3a9e908 | ||
|
|
e237b0f0aa | ||
|
|
5afc868562 | ||
|
|
a8f1831a62 | ||
|
|
647dc488ff | ||
|
|
f740776105 | ||
|
|
11559f294a | ||
|
|
f413b23f06 | ||
|
|
2056ebc803 | ||
|
|
a4358deb0d | ||
|
|
44b4a4e028 | ||
|
|
6e3b60a91a | ||
|
|
478909697b | ||
|
|
71efef723b | ||
|
|
25a89c3ff1 | ||
|
|
1056027d4d | ||
|
|
17d0037bd5 |
No files matched your search
@@ -63,6 +63,11 @@ menu/driverspzarch.c
|
||||
/media/shaders_glsl/
|
||||
/obj-w32/
|
||||
|
||||
# Wii U
|
||||
*.depend
|
||||
*.rpx
|
||||
wiiu/wut/elf2rpl/elf2rpl
|
||||
|
||||
# Ctags
|
||||
/tags
|
||||
|
||||
|
||||
+60
-31
@@ -1,71 +1,100 @@
|
||||
# 1.5.0+ (future)
|
||||
- AUTOSAVE/SRAM - Fix bug #3829 / #4820 (https://github.com/libretro/RetroArch/issues/3829)
|
||||
- ENDIANNESS: Fixed database scanning. Should fix scanning on PS3/WiiU/Wii, etc.
|
||||
- NET: Fix bug #4703 (https://github.com/libretro/RetroArch/issues/4703)
|
||||
- ANDROID: Runtime permission checking
|
||||
# 1.6.2 (future)
|
||||
- IOS: Fix GL regression - 32bit color format cores were no longer rendering
|
||||
- CHEEVOS: Add support for N64 cheevos and other small fixes.
|
||||
- CHEEVOS: Add 'Achievements -> Achievements Verbose Mode'. Ability to display cheevos related messages in OSD, useful for RetroAchievements users.
|
||||
- AUDIO: Audio mixer's volume can now be independently increased/decreased, and muted.
|
||||
- AUDIO: Mute now no longer disables/enables audio but instead properly mutes the audio volume. Mute is also independent from the audio mixer volume.
|
||||
- INPUT: Add mouse index selection; ability now to select between different mice
|
||||
- INPUT: Fix 'All Users Control Menu' setting
|
||||
- LINUX: Add a tinyalsa audio driver. Doesn't require asoundlib, should be self-contained and lower-level.
|
||||
- LOBBIES: Announce the RetroArch version too
|
||||
- LOCALIZATION: Add Traditional Chinese translation
|
||||
- LOCALIZATION: Update French translation
|
||||
- LOCALIZATION: Update Italian translation
|
||||
- LOCALIZATION: Update Japanese translation
|
||||
- LOCALIZATION: Update Russian translation
|
||||
- MENU: Add 'User Interface -> Views'. Ability to display/hide online updater and core updater options.
|
||||
- NETPLAY: Disconnecting one client shouldn't cause everyone to disconnect anymore
|
||||
- NETWORK: SSL/TLS support, disabled by default
|
||||
- SCANNER: Fix PS1 game scanning
|
||||
- SCANNER: Move content list builder into scanner task with progress, fixes menu freeze with large playlists
|
||||
- SDL2: Fix 'SDL2 driver does not see the hat on wired Xbox 360 controller"
|
||||
- SETTINGS: Fix regression 'Custom Viewport is no longer overridable per-core or per-game'
|
||||
- VITA: Add cheevos support
|
||||
- VITA: Add support for external USB if mounted
|
||||
- WAYLAND: Fix menu mouse input
|
||||
- WII: Add support for single-port 'PS1/PS2 to USB controller adapter'
|
||||
|
||||
# 1.6.0
|
||||
- ANDROID: Allow remotes to retain OK/Cancel position when menu_swap_ok_cancel is enabled
|
||||
- ANDROID: Improve autoconf fallback
|
||||
- ANDROID: Improve shield portable/gamepad device grouping workaround
|
||||
- ANDROID: Allow remotes to retain OK/Cancel position when menu_swap_ok_cancel is enabled
|
||||
- LOCALIZATION: Update/finish French translation
|
||||
- LOCALIZATION: Update German translation
|
||||
- LOCALIZATION: Update Japanese translation
|
||||
- LOCALIZATION/GUI: Korean font should display properly now with XMB/MaterialUI's default font
|
||||
- MENU: Improved rendering for XMB ribbon; using additive blending (Vulkan/GL)
|
||||
- OSX/MACOS: Fixes serious memory leak
|
||||
- WINDOWS: Added WASAPI audio driver for low-latency audio. Both shared and exclusive mode.
|
||||
- WINDOWS: Added RawInput input driver for low-latency, low-level input.
|
||||
- WINDOWS: Core mouse input should be relative again in cores
|
||||
- MISC: Various frontend optimizations.
|
||||
- VIDEO: Fix threaded video regression; tickering of menu entries would no longer work.
|
||||
- WII: Fix crashing issues which could occur with the dummy core
|
||||
- WIIU: HID Controller support
|
||||
- WIIU: XMB/MaterialUI menu driver support
|
||||
- WIIU: Initial network/netplay support
|
||||
- ANDROID: Runtime permission checking
|
||||
- AUDIO: Audio mixer support. Mix up to 8 streams with the game's audio.
|
||||
- AUTOSAVE/SRAM - Fix bug #3829 / #4820 (https://github.com/libretro/RetroArch/issues/3829)
|
||||
- ENDIANNESS: Fixed database scanning. Should fix scanning on PS3/WiiU/Wii, etc.
|
||||
- LOBBIES: Fallback to filename based matching if no CRC matches are found (for people making playlists by hand)
|
||||
- LOBBIES: GUI refinement, show stop hosting when a host has been started, show disconnect when playing as client
|
||||
- LOBBIES: if the game is already loaded it will try to connect directly instead of re-loading content (non-fullpath cores only)
|
||||
- LOBBIES: unify both netplay menus
|
||||
- LOCALIZATION/GUI: Korean font should display properly now with XMB/MaterialUI's default font
|
||||
- LOCALIZATION: Update German translation
|
||||
- LOCALIZATION: Update Japanese translation
|
||||
- LOCALIZATION: Update Russian translation
|
||||
- LOCALIZATION: Update/finish French translation
|
||||
- MENU: Improved rendering for XMB ribbon; using additive blending (Vulkan/GL)
|
||||
- MISC: Various frontend optimizations.
|
||||
- NET: Fix bug #4703 (https://github.com/libretro/RetroArch/issues/4703)
|
||||
- OSX/MACOS: Fixes serious memory leak
|
||||
- THUMBNAILS: Thumbnails show up now in Load Content -> Collection, Information -> Database
|
||||
- VITA: Fix slow I/O
|
||||
- VIDEO: Fix threaded video regression; tickering of menu entries would no longer work.
|
||||
- VITA: Fix 30fps menu (poke into input now instead of reading the entire input buffer which apparently is slow)
|
||||
- VITA: Fix frame throttle
|
||||
- VULKAN: Unicode font rendering support. Should fix bad character encoding for French characters, etc.
|
||||
- VITA: Fix slow I/O
|
||||
- VULKAN: Fix some crashes on loading some thumbnails
|
||||
- VULKAN: Unicode font rendering support. Should fix bad character encoding for French characters, etc.
|
||||
- WII: Fix crashing issues which could occur with the dummy core
|
||||
- WIIU: HID Controller support
|
||||
- WIIU: Initial network/netplay support
|
||||
- WIIU: XMB/MaterialUI menu driver support
|
||||
- WINDOWS: Added RawInput input driver for low-latency, low-level input.
|
||||
- WINDOWS: Added WASAPI audio driver for low-latency audio. Both shared and exclusive mode.
|
||||
- WINDOWS: Core mouse input should be relative again in cores
|
||||
|
||||
# 1.5.0
|
||||
- MOBILE: Single-tap for menu entry selection
|
||||
- MOBILE: Long-tap a setting to reset to default
|
||||
- ANDROID: Autoconf fallback
|
||||
- ANDROID: Mouse support / Emulated mouse support
|
||||
- AUTOCONF: Fix partial matches for pad name
|
||||
- CHEEVOS: Fix crashes in the cheevos description menu
|
||||
- CHEEVOS: WIP leaderboards support
|
||||
- COMMON: Threading fixes
|
||||
- COMMON: 9-slice texture drawing support
|
||||
- COMMON: Threading fixes
|
||||
- CORETEXT/APPLE: Ability to load menu display font drivers and loading of custom font.
|
||||
- DOS: Add keyboard driver
|
||||
- DOS: Improve color accuracy and scaling
|
||||
- GUI: Various settings are now only visible when advanced settings is enabled
|
||||
- GUI: Allow changing icon theme on the fly
|
||||
- GUI: Add a symbol page in the OSK
|
||||
- GUI: Allow changing icon theme on the fly
|
||||
- GUI: Better dialogs for XMB
|
||||
- GUI: Various settings are now only visible when advanced settings is enabled
|
||||
- LOCALIZATION: Add/update Korean translation
|
||||
- LOCALIZATION: Rewrite German translation
|
||||
- LOCALIZATION: Update several English sublabels
|
||||
- LOCALIZATION: Update several Japanese labels
|
||||
- MOBILE: Long-tap a setting to reset to default
|
||||
- MOBILE: Single-tap for menu entry selection
|
||||
- NET: Allow manual netplay content loading
|
||||
- NET: Announcing network games to the public lobby is optional now
|
||||
- NET: Bake in miniupnpc
|
||||
- NET: Fix netplay join for contentless cores
|
||||
- NET: Fix netplay rooms being pushed on the wrong tab
|
||||
- NET: Lan games show next to lobbies with (lan) and connect via the private IP address
|
||||
- NET: Use new lobby system with MITM support
|
||||
- NET: Fix netplay rooms being pushed on the wrong tab
|
||||
- NUKLEAR: Update to current version
|
||||
- SCANNER: Always add 7z & zip to supported extensions
|
||||
- VULKAN: Find supported composite alpha in swapchain
|
||||
- VULKAN: Add snow/bokeh shader pipeline effects - at parity with GL now
|
||||
- VULKAN: Find supported composite alpha in swapchain
|
||||
- WIIU: Keyboard support
|
||||
- WINDOWS: Logging to file no longer spawns an empty window
|
||||
- WINDOWS: Fix loading of core/content via file menu
|
||||
- WINDOWS: Logging to file no longer spawns an empty window
|
||||
|
||||
# 1.4.1
|
||||
+221
-18
@@ -105,7 +105,7 @@ endif
|
||||
|
||||
ifneq ($(findstring BSD,$(OS)),)
|
||||
BSD_LOCAL_INC += -I/usr/local/include
|
||||
OBJ += frontend/drivers/platform_bsd.o
|
||||
HAVE_UNIX = 1
|
||||
endif
|
||||
|
||||
ifneq ($(findstring Darwin,$(OS)),)
|
||||
@@ -119,11 +119,13 @@ ifneq ($(findstring Linux,$(OS)),)
|
||||
LIBS += -lrt
|
||||
OBJ += input/drivers/linuxraw_input.o \
|
||||
input/common/linux_common.o \
|
||||
input/common/epoll_common.o \
|
||||
input/drivers_joypad/linuxraw_joypad.o \
|
||||
frontend/drivers/platform_linux.o
|
||||
input/drivers_joypad/linuxraw_joypad.o
|
||||
HAVE_UNIX = 1
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_UNIX), 1)
|
||||
OBJ += frontend/drivers/platform_unix.o
|
||||
endif
|
||||
|
||||
ifeq ($(findstring Haiku,$(OS)),)
|
||||
LIBS += -lm
|
||||
@@ -157,7 +159,6 @@ OBJ += frontend/frontend.o \
|
||||
retroarch.o \
|
||||
dirs.o \
|
||||
paths.o \
|
||||
input/input_keyboard.o \
|
||||
command.o \
|
||||
msg_hash.o \
|
||||
intl/msg_hash_us.o \
|
||||
@@ -174,9 +175,9 @@ OBJ += frontend/frontend.o \
|
||||
$(LIBRETRO_COMM_DIR)/file/retro_dirent.o \
|
||||
$(LIBRETRO_COMM_DIR)/streams/stdin_stream.o \
|
||||
$(LIBRETRO_COMM_DIR)/streams/file_stream.o \
|
||||
$(LIBRETRO_COMM_DIR)/streams/file_stream_transforms.o \
|
||||
$(LIBRETRO_COMM_DIR)/streams/interface_stream.o \
|
||||
$(LIBRETRO_COMM_DIR)/streams/memory_stream.o \
|
||||
$(LIBRETRO_COMM_DIR)/file/retro_stat.o \
|
||||
$(LIBRETRO_COMM_DIR)/lists/string_list.o \
|
||||
$(LIBRETRO_COMM_DIR)/string/stdstring.o \
|
||||
$(LIBRETRO_COMM_DIR)/memmap/memalign.o \
|
||||
@@ -206,7 +207,6 @@ OBJ += frontend/frontend.o \
|
||||
gfx/drivers_font_renderer/bitmapfont.o \
|
||||
tasks/task_autodetect.o \
|
||||
input/input_autodetect_builtin.o \
|
||||
input/input_joypad_driver.o \
|
||||
input/input_config.o \
|
||||
input/input_keymaps.o \
|
||||
input/input_remapping.o \
|
||||
@@ -274,7 +274,8 @@ OBJ += intl/msg_hash_de.o \
|
||||
intl/msg_hash_pt_pt.o \
|
||||
intl/msg_hash_ru.o \
|
||||
intl/msg_hash_vn.o \
|
||||
intl/msg_hash_chs.o
|
||||
intl/msg_hash_chs.o \
|
||||
intl/msg_hash_cht.o
|
||||
|
||||
endif
|
||||
|
||||
@@ -334,6 +335,204 @@ OBJ += libretro-db/bintree.o \
|
||||
tasks/task_database_cue.o
|
||||
endif
|
||||
|
||||
ifneq ($(C89_BUILD), 1)
|
||||
HAVE_LIBUI = 0
|
||||
HAVE_GTKPLUS = 0
|
||||
|
||||
ifeq ($(HAVE_SSL), 1)
|
||||
DEFINES += -DHAVE_SSL
|
||||
DEFINES += -DMBEDTLS_SSL_DEBUG_ALL
|
||||
|
||||
# MinGW requires this for some reason, even though the include paths are relative to the source
|
||||
INCLUDE_DIRS += -Ideps/mbedtls
|
||||
|
||||
OBJS_TLS_CRYPTO = deps/mbedtls/aes.o deps/mbedtls/aesni.o deps/mbedtls/arc4.o \
|
||||
deps/mbedtls/asn1parse.o deps/mbedtls/asn1write.o deps/mbedtls/base64.o \
|
||||
deps/mbedtls/bignum.o deps/mbedtls/blowfish.o deps/mbedtls/camellia.o \
|
||||
deps/mbedtls/ccm.o deps/mbedtls/cipher.o deps/mbedtls/cipher_wrap.o \
|
||||
deps/mbedtls/cmac.o deps/mbedtls/ctr_drbg.o deps/mbedtls/des.o \
|
||||
deps/mbedtls/dhm.o deps/mbedtls/ecdh.o deps/mbedtls/ecdsa.o \
|
||||
deps/mbedtls/ecjpake.o deps/mbedtls/ecp.o \
|
||||
deps/mbedtls/ecp_curves.o deps/mbedtls/entropy.o deps/mbedtls/entropy_poll.o \
|
||||
deps/mbedtls/error.o deps/mbedtls/gcm.o deps/mbedtls/havege.o \
|
||||
deps/mbedtls/hmac_drbg.o deps/mbedtls/md.o deps/mbedtls/md2.o \
|
||||
deps/mbedtls/md4.o deps/mbedtls/md5.o deps/mbedtls/md_wrap.o \
|
||||
deps/mbedtls/memory_buffer_alloc.o deps/mbedtls/oid.o \
|
||||
deps/mbedtls/padlock.o deps/mbedtls/pem.o deps/mbedtls/pk.o \
|
||||
deps/mbedtls/pk_wrap.o deps/mbedtls/pkcs12.o deps/mbedtls/pkcs5.o \
|
||||
deps/mbedtls/pkparse.o deps/mbedtls/pkwrite.o deps/mbedtls/platform.o \
|
||||
deps/mbedtls/ripemd160.o deps/mbedtls/rsa.o deps/mbedtls/sha1.o \
|
||||
deps/mbedtls/sha256.o deps/mbedtls/sha512.o deps/mbedtls/threading.o \
|
||||
deps/mbedtls/timing.o deps/mbedtls/version.o \
|
||||
deps/mbedtls/version_features.o deps/mbedtls/xtea.o
|
||||
|
||||
OBJS_TLS_X509 = deps/mbedtls/certs.o deps/mbedtls/pkcs11.o deps/mbedtls/x509.o \
|
||||
deps/mbedtls/x509_create.o deps/mbedtls/x509_crl.o deps/mbedtls/x509_crt.o \
|
||||
deps/mbedtls/x509_csr.o deps/mbedtls/x509write_crt.o deps/mbedtls/x509write_csr.o
|
||||
|
||||
OBJS_TLS = deps/mbedtls/debug.o deps/mbedtls/net_sockets.o \
|
||||
deps/mbedtls/ssl_cache.o deps/mbedtls/ssl_ciphersuites.o \
|
||||
deps/mbedtls/ssl_cli.o deps/mbedtls/ssl_cookie.o \
|
||||
deps/mbedtls/ssl_srv.o deps/mbedtls/ssl_ticket.o \
|
||||
deps/mbedtls/ssl_tls.o
|
||||
|
||||
OBJ += $(OBJS_TLS_CRYPTO) $(OBJS_TLS_X509) $(OBJS_TLS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_LIBUI), 1)
|
||||
ifeq ($(HAVE_GTKPLUS), 1)
|
||||
CFLAGS += -I/usr/include/gtk-3.0 -I/usr/include/pango-1.0 -I/usr/include/cairo -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/atk-1.0
|
||||
LIBS += -lgtk-3 -lgdk-3 -lpangocairo-1.0 -lpango-1.0 -latk-1.0 -lcairo-gobject -lcairo -lgdk_pixbuf-2.0 -lgio-2.0 -lgobject-2.0 -lglib-2.0
|
||||
OBJ += deps/libui/gtk/alloc.o \
|
||||
deps/libui/gtk/area.o \
|
||||
deps/libui/gtk/box.o \
|
||||
deps/libui/gtk/button.o \
|
||||
deps/libui/gtk/cellrendererbutton.o \
|
||||
deps/libui/gtk/checkbox.o \
|
||||
deps/libui/gtk/child.o \
|
||||
deps/libui/gtk/colorbutton.o \
|
||||
deps/libui/gtk/combobox.o \
|
||||
deps/libui/gtk/control.o \
|
||||
deps/libui/gtk/datetimepicker.o \
|
||||
deps/libui/gtk/debug.o \
|
||||
deps/libui/gtk/draw.o \
|
||||
deps/libui/gtk/drawmatrix.o \
|
||||
deps/libui/gtk/drawpath.o \
|
||||
deps/libui/gtk/drawtext.o \
|
||||
deps/libui/gtk/editablecombo.o \
|
||||
deps/libui/gtk/entry.o \
|
||||
deps/libui/gtk/fontbutton.o \
|
||||
deps/libui/gtk/form.o \
|
||||
deps/libui/gtk/future.o \
|
||||
deps/libui/gtk/graphemes.o \
|
||||
deps/libui/gtk/grid.o \
|
||||
deps/libui/gtk/group.o \
|
||||
deps/libui/gtk/image.o \
|
||||
deps/libui/gtk/label.o \
|
||||
deps/libui/gtk/main.o \
|
||||
deps/libui/gtk/menu.o \
|
||||
deps/libui/gtk/multilineentry.o \
|
||||
deps/libui/gtk/progressbar.o \
|
||||
deps/libui/gtk/radiobuttons.o \
|
||||
deps/libui/gtk/separator.o \
|
||||
deps/libui/gtk/slider.o \
|
||||
deps/libui/gtk/spinbox.o \
|
||||
deps/libui/gtk/stddialogs.o \
|
||||
deps/libui/gtk/tab.o \
|
||||
deps/libui/gtk/text.o \
|
||||
deps/libui/gtk/util.o \
|
||||
deps/libui/gtk/window.o
|
||||
endif
|
||||
|
||||
ifneq ($(findstring Win32,$(OS)),)
|
||||
OBJ += deps/libui/win32/alloc.o \
|
||||
deps/libui/win32/area.o \
|
||||
deps/libui/win32/areadraw.o \
|
||||
deps/libui/win32/areaevents.o \
|
||||
deps/libui/win32/areascroll.o \
|
||||
deps/libui/win32/areautil.o \
|
||||
deps/libui/win32/box.o \
|
||||
deps/libui/win32/button.o \
|
||||
deps/libui/win32/checkbox.o \
|
||||
deps/libui/win32/colorbutton.o \
|
||||
deps/libui/win32/colordialog.o \
|
||||
deps/libui/win32/combobox.o \
|
||||
deps/libui/win32/container.o \
|
||||
deps/libui/win32/control.o \
|
||||
deps/libui/win32/d2dscratch.o \
|
||||
deps/libui/win32/datetimepicker.o \
|
||||
deps/libui/win32/debug.o \
|
||||
deps/libui/win32/draw.o \
|
||||
deps/libui/win32/drawmatrix.o \
|
||||
deps/libui/win32/drawpath.o \
|
||||
deps/libui/win32/drawtext.o \
|
||||
deps/libui/win32/dwrite.o \
|
||||
deps/libui/win32/editablecombo.o \
|
||||
deps/libui/win32/entry.o \
|
||||
deps/libui/win32/events.o \
|
||||
deps/libui/win32/fontbutton.o \
|
||||
deps/libui/win32/fontdialog.o \
|
||||
deps/libui/win32/form.o \
|
||||
deps/libui/win32/graphemes.o \
|
||||
deps/libui/win32/grid.o \
|
||||
deps/libui/win32/group.o \
|
||||
deps/libui/win32/init.o \
|
||||
deps/libui/win32/label.o \
|
||||
deps/libui/win32/main.o \
|
||||
deps/libui/win32/menu.o \
|
||||
deps/libui/win32/multilineentry.o \
|
||||
deps/libui/win32/parent.o \
|
||||
deps/libui/win32/progressbar.o \
|
||||
deps/libui/win32/radiobuttons.o \
|
||||
deps/libui/win32/separator.o \
|
||||
deps/libui/win32/sizing.o \
|
||||
deps/libui/win32/slider.o \
|
||||
deps/libui/win32/spinbox.o \
|
||||
deps/libui/win32/stddialogs.o \
|
||||
deps/libui/win32/tab.o \
|
||||
deps/libui/win32/tabpage.o \
|
||||
deps/libui/win32/text.o \
|
||||
deps/libui/win32/utf16.o \
|
||||
deps/libui/win32/utilwin.o \
|
||||
deps/libui/win32/window.o \
|
||||
deps/libui/win32/winpublic.o \
|
||||
deps/libui/win32/winutil.o
|
||||
LIBS += -luxtheme -ld2d1 -ldwrite -lusp10
|
||||
endif
|
||||
|
||||
ifneq ($(findstring Darwin,$(OS)),)
|
||||
OBJ += deps/libui/darwin/alloc.o \
|
||||
deps/libui/darwin/area.o \
|
||||
deps/libui/darwin/areaevents.o \
|
||||
deps/libui/darwin/autolayout.o \
|
||||
deps/libui/darwin/box.o \
|
||||
deps/libui/darwin/button.o \
|
||||
deps/libui/darwin/checkbox.o \
|
||||
deps/libui/darwin/colorbutton.o \
|
||||
deps/libui/darwin/combobox.o \
|
||||
deps/libui/darwin/control.o \
|
||||
deps/libui/darwin/datetimepicker.o \
|
||||
deps/libui/darwin/debug.o \
|
||||
deps/libui/darwin/draw.o \
|
||||
deps/libui/darwin/drawtext.o \
|
||||
deps/libui/darwin/editablecombo.o \
|
||||
deps/libui/darwin/entry.o \
|
||||
deps/libui/darwin/fontbutton.o \
|
||||
deps/libui/darwin/form.o \
|
||||
deps/libui/darwin/grid.o \
|
||||
deps/libui/darwin/group.o \
|
||||
deps/libui/darwin/image.o \
|
||||
deps/libui/darwin/label.o \
|
||||
deps/libui/darwin/main.o \
|
||||
deps/libui/darwin/map.o \
|
||||
deps/libui/darwin/menu.o \
|
||||
deps/libui/darwin/multilineentry.o \
|
||||
deps/libui/darwin/progressbar.o \
|
||||
deps/libui/darwin/radiobuttons.o \
|
||||
deps/libui/darwin/scrollview.o \
|
||||
deps/libui/darwin/separator.o \
|
||||
deps/libui/darwin/slider.o \
|
||||
deps/libui/darwin/spinbox.o \
|
||||
deps/libui/darwin/stddialogs.o \
|
||||
deps/libui/darwin/tab.o \
|
||||
deps/libui/darwin/text.o \
|
||||
deps/libui/darwin/util.o \
|
||||
deps/libui/darwin/window.o \
|
||||
deps/libui/darwin/winmoveresize.o
|
||||
endif
|
||||
|
||||
OBJ += deps/libui/common/areaevents.o \
|
||||
deps/libui/common/control.o \
|
||||
deps/libui/common/debug.o \
|
||||
deps/libui/common/matrix.o \
|
||||
deps/libui/common/shouldquit.o \
|
||||
deps/libui/common/userbugs.o
|
||||
|
||||
OBJ += deps/libui/libui_main.o
|
||||
endif
|
||||
endif
|
||||
|
||||
|
||||
# Miscellaneous
|
||||
|
||||
ifeq ($(HAVE_STDIN_CMD), 1)
|
||||
@@ -370,9 +569,7 @@ endif
|
||||
|
||||
ifeq ($(HAVE_OSS), 1)
|
||||
OBJ += audio/drivers/oss.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_OSS_BSD), 1)
|
||||
else ifeq ($(HAVE_OSS_BSD), 1)
|
||||
OBJ += audio/drivers/oss.o
|
||||
endif
|
||||
|
||||
@@ -386,6 +583,11 @@ endif
|
||||
DEFINES += $(ALSA_CFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_TINYALSA), 1)
|
||||
OBJ += audio/drivers/tinyalsa.o
|
||||
DEFINES += -DHAVE_TINYALSA
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_ROAR), 1)
|
||||
OBJ += audio/drivers/roar.o
|
||||
LIBS += $(ROAR_LIBS)
|
||||
@@ -645,7 +847,8 @@ endif
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_WAYLAND), 1)
|
||||
OBJ += gfx/drivers_context/wayland_ctx.o
|
||||
OBJ += gfx/drivers_context/wayland_ctx.o \
|
||||
input/drivers/wayland_input.o
|
||||
DEFINES += $(WAYLAND_CFLAGS) $(WAYLAND_CURSOR_CFLAGS)
|
||||
LIBS += $(WAYLAND_LIBS) $(WAYLAND_CURSOR_LIBS)
|
||||
endif
|
||||
@@ -708,7 +911,6 @@ ifeq ($(HAVE_UDEV), 1)
|
||||
DEFINES += $(UDEV_CFLAGS)
|
||||
LIBS += $(UDEV_LIBS)
|
||||
OBJ += input/drivers/udev_input.o \
|
||||
input/common/udev_common.o \
|
||||
input/drivers_joypad/udev_joypad.o
|
||||
endif
|
||||
|
||||
@@ -737,10 +939,10 @@ endif
|
||||
|
||||
ifeq ($(HAVE_HID), 1)
|
||||
DEFINES += -DHAVE_HID
|
||||
OBJ += input/input_hid_driver.o \
|
||||
input/drivers_joypad/hid_joypad.o \
|
||||
OBJ += input/drivers_joypad/hid_joypad.o \
|
||||
input/connect/joypad_connection.o \
|
||||
input/connect/connect_ps2adapter.o \
|
||||
input/connect/connect_psxadapter.o \
|
||||
input/connect/connect_ps3.o \
|
||||
input/connect/connect_ps4.o \
|
||||
input/connect/connect_wii.o \
|
||||
@@ -1037,7 +1239,6 @@ endif
|
||||
|
||||
ifeq ($(HAVE_D3D9), 1)
|
||||
OBJ += gfx/drivers/d3d.o \
|
||||
gfx/video_renderchain_driver.o \
|
||||
gfx/common/d3d_common.o \
|
||||
gfx/drivers_font/d3d_w32_font.o \
|
||||
gfx/drivers_context/d3d_ctx.o
|
||||
@@ -1073,11 +1274,9 @@ ifeq ($(HAVE_7ZIP),1)
|
||||
HAVE_COMPRESSION = 1
|
||||
DEFINES += -DHAVE_7ZIP
|
||||
7ZOBJ = $(DEPS_DIR)/7zip/7zIn.o \
|
||||
$(DEPS_DIR)/7zip/7zAlloc.o \
|
||||
$(DEPS_DIR)/7zip/Bra86.o \
|
||||
$(DEPS_DIR)/7zip/7zFile.o \
|
||||
$(DEPS_DIR)/7zip/7zStream.o \
|
||||
$(DEPS_DIR)/7zip/7zBuf2.o \
|
||||
$(DEPS_DIR)/7zip/LzmaDec.o \
|
||||
$(DEPS_DIR)/7zip/7zCrcOpt.o \
|
||||
$(DEPS_DIR)/7zip/Bra.o \
|
||||
@@ -1183,6 +1382,10 @@ ifeq ($(HAVE_NETWORKING), 1)
|
||||
tasks/task_wifi.o \
|
||||
tasks/task_netplay_find_content.o
|
||||
|
||||
ifeq ($(HAVE_SSL), 1)
|
||||
OBJ += $(LIBRETRO_COMM_DIR)/net/net_socket_ssl.o
|
||||
endif
|
||||
|
||||
ifneq ($(HAVE_SOCKET_LEGACY),1)
|
||||
OBJ += $(LIBRETRO_COMM_DIR)/net/net_ifinfo.o
|
||||
endif
|
||||
|
||||
@@ -34,7 +34,6 @@ OBJ := ctr/ctr_system.o \
|
||||
libretro-common/compat/compat_strl.o \
|
||||
libretro-common/file/config_file.o \
|
||||
libretro-common/streams/file_stream.o \
|
||||
libretro-common/file/retro_stat.o \
|
||||
libretro-common/hash/rhash.o \
|
||||
file_path_str.o \
|
||||
verbosity.o
|
||||
|
||||
@@ -27,7 +27,6 @@ PPU_SRCS = frontend/frontend_salamander.c \
|
||||
libretro-common/lists/dir_list.c \
|
||||
libretro-common/lists/string_list.c \
|
||||
libretro-common/file/retro_dirent.c \
|
||||
libretro-common/file/retro_stat.c \
|
||||
libretro-common/hash/rhash.c \
|
||||
libretro-common/string/stdstring.c \
|
||||
libretro-common/compat/compat_strl.c \
|
||||
|
||||
@@ -44,7 +44,6 @@ OBJS = frontend/frontend_salamander.o \
|
||||
libretro-common/compat/compat_strcasestr.o \
|
||||
libretro-common/file/config_file.o \
|
||||
libretro-common/streams/file_stream.o \
|
||||
libretro-common/file/retro_stat.o \
|
||||
libretro-common/hash/rhash.o \
|
||||
file_path_str.o \
|
||||
verbosity.o \
|
||||
|
||||
@@ -45,6 +45,7 @@ else
|
||||
HAVE_THREADS := 1
|
||||
HAVE_LIBRETRODB := 1
|
||||
HAVE_CC_RESAMPLER := 1
|
||||
HAVE_CHEEVOS := 1
|
||||
RARCH_CONSOLE := 1
|
||||
|
||||
ifeq ($(DEBUG), 1)
|
||||
|
||||
@@ -45,7 +45,6 @@ OBJS = frontend/frontend_salamander.o \
|
||||
libretro-common/compat/compat_strcasestr.o \
|
||||
libretro-common/file/config_file.o \
|
||||
libretro-common/streams/file_stream.o \
|
||||
libretro-common/file/retro_stat.o \
|
||||
libretro-common/hash/rhash.o \
|
||||
file_path_str.o \
|
||||
verbosity.o
|
||||
|
||||
@@ -51,7 +51,6 @@ OBJ = frontend/frontend_salamander.o \
|
||||
libretro-common/lists/dir_list.o \
|
||||
libretro-common/streams/file_stream.o \
|
||||
libretro-common/file/retro_dirent.o \
|
||||
libretro-common/file/retro_stat.o \
|
||||
libretro-common/compat/compat_strl.o \
|
||||
libretro-common/compat/compat_strcasestr.o \
|
||||
libretro-common/file/config_file.o \
|
||||
|
||||
@@ -11,6 +11,7 @@ PC_DEVELOPMENT_TCP_PORT ?=
|
||||
OBJ :=
|
||||
OBJ += wiiu/system/memory.o
|
||||
OBJ += wiiu/system/exception_handler.o
|
||||
OBJ += wiiu/system/missing_libc_functions.o
|
||||
OBJ += wiiu/fs/sd_fat_devoptab.o
|
||||
OBJ += wiiu/fs/fs_utils.o
|
||||
OBJ += wiiu/controller_patcher/ControllerPatcher.o
|
||||
|
||||
@@ -25,7 +25,6 @@ OBJ += libretro-common/file/retro_dirent.o
|
||||
OBJ += libretro-common/compat/compat_strl.o
|
||||
OBJ += libretro-common/file/config_file.o
|
||||
OBJ += libretro-common/streams/file_stream.o
|
||||
OBJ += libretro-common/file/retro_stat.o
|
||||
OBJ += libretro-common/hash/rhash.o
|
||||
OBJ += file_path_str.o
|
||||
OBJ += verbosity.o
|
||||
|
||||
+60
-33
@@ -55,6 +55,9 @@ static const audio_driver_t *audio_drivers[] = {
|
||||
&audio_alsathread,
|
||||
#endif
|
||||
#endif
|
||||
#ifdef HAVE_TINYALSA
|
||||
&audio_tinyalsa,
|
||||
#endif
|
||||
#if defined(HAVE_OSS) || defined(HAVE_OSS_BSD)
|
||||
&audio_oss,
|
||||
#endif
|
||||
@@ -148,6 +151,7 @@ static size_t audio_driver_buffer_size = 0;
|
||||
static size_t audio_driver_data_ptr = 0;
|
||||
|
||||
static bool audio_driver_control = false;
|
||||
static bool audio_driver_mixer_mute_enable = false;
|
||||
static bool audio_driver_mute_enable = false;
|
||||
static bool audio_driver_use_float = false;
|
||||
static bool audio_driver_active = false;
|
||||
@@ -157,6 +161,7 @@ static bool audio_mixer_active = false;
|
||||
static float audio_driver_rate_control_delta = 0.0f;
|
||||
static float audio_driver_input = 0.0f;
|
||||
static float audio_driver_volume_gain = 0.0f;
|
||||
static float audio_driver_mixer_volume_gain = 0.0f;
|
||||
|
||||
static float *audio_driver_input_data = NULL;
|
||||
static float *audio_driver_output_samples_buf = NULL;
|
||||
@@ -533,6 +538,8 @@ static bool audio_driver_flush(const int16_t *data, size_t samples)
|
||||
bool is_slowmotion = false;
|
||||
const void *output_data = NULL;
|
||||
unsigned output_frames = 0;
|
||||
float audio_volume_gain = !audio_driver_mute_enable ?
|
||||
audio_driver_volume_gain : 0.0f;
|
||||
|
||||
src_data.data_in = NULL;
|
||||
src_data.data_out = NULL;
|
||||
@@ -546,13 +553,13 @@ static bool audio_driver_flush(const int16_t *data, size_t samples)
|
||||
runloop_get_status(&is_paused, &is_idle, &is_slowmotion,
|
||||
&is_perfcnt_enable);
|
||||
|
||||
if (is_paused || audio_driver_mute_enable)
|
||||
if (is_paused)
|
||||
return true;
|
||||
if (!audio_driver_active || !audio_driver_input_data)
|
||||
return false;
|
||||
|
||||
convert_s16_to_float(audio_driver_input_data, data, samples,
|
||||
audio_driver_volume_gain);
|
||||
audio_volume_gain);
|
||||
|
||||
src_data.data_in = audio_driver_input_data;
|
||||
src_data.input_frames = samples >> 1;
|
||||
@@ -621,7 +628,14 @@ static bool audio_driver_flush(const int16_t *data, size_t samples)
|
||||
audio_driver_resampler->process(audio_driver_resampler_data, &src_data);
|
||||
|
||||
if (audio_mixer_active)
|
||||
audio_mixer_mix(audio_driver_output_samples_buf, src_data.output_frames);
|
||||
{
|
||||
bool override = audio_driver_mixer_mute_enable ? true :
|
||||
(audio_driver_mixer_volume_gain != 0.0f) ? true : false;
|
||||
float mixer_gain = !audio_driver_mixer_mute_enable ?
|
||||
audio_driver_mixer_volume_gain : 0.0f;
|
||||
audio_mixer_mix(audio_driver_output_samples_buf,
|
||||
src_data.output_frames, mixer_gain, override);
|
||||
}
|
||||
|
||||
output_data = audio_driver_output_samples_buf;
|
||||
output_frames = (unsigned)src_data.output_frames;
|
||||
@@ -727,11 +741,6 @@ size_t audio_driver_sample_batch_rewind(const int16_t *data, size_t frames)
|
||||
return frames;
|
||||
}
|
||||
|
||||
void audio_driver_set_volume_gain(float gain)
|
||||
{
|
||||
audio_driver_volume_gain = gain;
|
||||
}
|
||||
|
||||
void audio_driver_dsp_filter_free(void)
|
||||
{
|
||||
if (audio_driver_dsp)
|
||||
@@ -945,15 +954,23 @@ static void audio_mixer_play_stop_cb(audio_mixer_sound_t *sound, unsigned reason
|
||||
case AUDIO_MIXER_SOUND_FINISHED:
|
||||
audio_mixer_destroy(sound);
|
||||
|
||||
if (audio_mixer_streams[idx].buf != NULL)
|
||||
free(audio_mixer_streams[idx].buf);
|
||||
audio_mixer_streams[idx].state = AUDIO_STREAM_STATE_NONE;
|
||||
audio_mixer_streams[idx].volume = 0.0f;
|
||||
audio_mixer_streams[idx].buf = NULL;
|
||||
audio_mixer_streams[idx].stop_cb = NULL;
|
||||
audio_mixer_streams[idx].handle = NULL;
|
||||
audio_mixer_streams[idx].voice = NULL;
|
||||
audio_mixer_current_max_idx--;
|
||||
if (idx >= 0)
|
||||
{
|
||||
unsigned i = (unsigned)idx;
|
||||
|
||||
#if 0
|
||||
if (audio_mixer_streams[i].buf != NULL)
|
||||
free(audio_mixer_streams[i].buf);
|
||||
#endif
|
||||
|
||||
audio_mixer_streams[i].state = AUDIO_STREAM_STATE_NONE;
|
||||
audio_mixer_streams[i].volume = 0.0f;
|
||||
audio_mixer_streams[i].buf = NULL;
|
||||
audio_mixer_streams[i].stop_cb = NULL;
|
||||
audio_mixer_streams[i].handle = NULL;
|
||||
audio_mixer_streams[i].voice = NULL;
|
||||
audio_mixer_current_max_idx--;
|
||||
}
|
||||
break;
|
||||
case AUDIO_MIXER_SOUND_STOPPED:
|
||||
break;
|
||||
@@ -986,10 +1003,10 @@ bool audio_driver_mixer_add_stream(audio_mixer_stream_params_t *params)
|
||||
switch (params->type)
|
||||
{
|
||||
case AUDIO_MIXER_TYPE_WAV:
|
||||
handle = audio_mixer_load_wav(buf, params->bufsize);
|
||||
handle = audio_mixer_load_wav(buf, (int32_t)params->bufsize);
|
||||
break;
|
||||
case AUDIO_MIXER_TYPE_OGG:
|
||||
handle = audio_mixer_load_ogg(buf, params->bufsize);
|
||||
handle = audio_mixer_load_ogg(buf, (int32_t)params->bufsize);
|
||||
break;
|
||||
case AUDIO_MIXER_TYPE_NONE:
|
||||
free(buf);
|
||||
@@ -1140,21 +1157,13 @@ bool audio_driver_has_callback(void)
|
||||
|
||||
bool audio_driver_toggle_mute(void)
|
||||
{
|
||||
bool new_mute_state = !audio_driver_mute_enable;
|
||||
if (!audio_driver_context_audio_data)
|
||||
return false;
|
||||
if (!audio_driver_active)
|
||||
return false;
|
||||
audio_driver_mute_enable = !audio_driver_mute_enable;
|
||||
return true;
|
||||
}
|
||||
|
||||
audio_driver_mute_enable = new_mute_state;
|
||||
|
||||
if (new_mute_state)
|
||||
command_event(CMD_EVENT_AUDIO_STOP, NULL);
|
||||
else if (!command_event(CMD_EVENT_AUDIO_START, NULL))
|
||||
{
|
||||
audio_driver_active = false;
|
||||
return false;
|
||||
}
|
||||
bool audio_driver_mixer_toggle_mute(void)
|
||||
{
|
||||
audio_driver_mixer_mute_enable = !audio_driver_mixer_mute_enable;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1254,10 +1263,26 @@ void audio_set_bool(enum audio_action action, bool val)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* db_to_gain:
|
||||
* @db : Decibels.
|
||||
*
|
||||
* Converts decibels to voltage gain.
|
||||
*
|
||||
* Returns: voltage gain value.
|
||||
**/
|
||||
#define db_to_gain(db) (powf(10.0f, (db) / 20.0f))
|
||||
|
||||
void audio_set_float(enum audio_action action, float val)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case AUDIO_ACTION_VOLUME_GAIN:
|
||||
audio_driver_volume_gain = db_to_gain(val);
|
||||
break;
|
||||
case AUDIO_ACTION_MIXER_VOLUME_GAIN:
|
||||
audio_driver_mixer_volume_gain = db_to_gain(val);
|
||||
break;
|
||||
case AUDIO_ACTION_RATE_CONTROL_DELTA:
|
||||
audio_driver_rate_control_delta = val;
|
||||
break;
|
||||
@@ -1285,6 +1310,8 @@ bool *audio_get_bool_ptr(enum audio_action action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case AUDIO_ACTION_MIXER_MUTE_ENABLE:
|
||||
return &audio_driver_mixer_mute_enable;
|
||||
case AUDIO_ACTION_MUTE_ENABLE:
|
||||
return &audio_driver_mute_enable;
|
||||
case AUDIO_ACTION_NONE:
|
||||
|
||||
@@ -38,7 +38,10 @@ enum audio_action
|
||||
{
|
||||
AUDIO_ACTION_NONE = 0,
|
||||
AUDIO_ACTION_RATE_CONTROL_DELTA,
|
||||
AUDIO_ACTION_MIXER_MUTE_ENABLE,
|
||||
AUDIO_ACTION_MUTE_ENABLE,
|
||||
AUDIO_ACTION_VOLUME_GAIN,
|
||||
AUDIO_ACTION_MIXER_VOLUME_GAIN,
|
||||
AUDIO_ACTION_MIXER
|
||||
};
|
||||
|
||||
@@ -201,8 +204,6 @@ size_t audio_driver_sample_batch_rewind(const int16_t *data, size_t frames);
|
||||
|
||||
bool audio_driver_mixer_extension_supported(const char *ext);
|
||||
|
||||
void audio_driver_set_volume_gain(float gain);
|
||||
|
||||
void audio_driver_dsp_filter_free(void);
|
||||
|
||||
void audio_driver_dsp_filter_init(const char *device);
|
||||
@@ -258,6 +259,7 @@ extern audio_driver_t audio_rsound;
|
||||
extern audio_driver_t audio_oss;
|
||||
extern audio_driver_t audio_alsa;
|
||||
extern audio_driver_t audio_alsathread;
|
||||
extern audio_driver_t audio_tinyalsa;
|
||||
extern audio_driver_t audio_roar;
|
||||
extern audio_driver_t audio_openal;
|
||||
extern audio_driver_t audio_opensl;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#define ALSA_PCM_NEW_HW_PARAMS_API
|
||||
#define ALSA_PCM_NEW_SW_PARAMS_API
|
||||
#include <sys/asoundlib.h>
|
||||
#include <retro_math.h>
|
||||
|
||||
#include "../audio_driver.h"
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <string.h>
|
||||
#include <malloc.h>
|
||||
#include <retro_miscellaneous.h>
|
||||
#include <retro_timers.h>
|
||||
|
||||
#include "../audio_driver.h"
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
#include <retro_inline.h>
|
||||
#include <retro_miscellaneous.h>
|
||||
#include <retro_timers.h>
|
||||
#include <rthreads/rthreads.h>
|
||||
#include <queues/fifo_queue.h>
|
||||
|
||||
|
||||
@@ -18,14 +18,11 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <jack/jack.h>
|
||||
#include <jack/types.h>
|
||||
#include <jack/ringbuffer.h>
|
||||
|
||||
#include <boolean.h>
|
||||
#include <retro_assert.h>
|
||||
#include <rthreads/rthreads.h>
|
||||
|
||||
#include "../audio_driver.h"
|
||||
@@ -76,7 +73,6 @@ static int process_cb(jack_nframes_t nframes, void *data)
|
||||
jack_nframes_t f;
|
||||
jack_default_audio_sample_t *out = (jack_default_audio_sample_t*)jack_port_get_buffer(jd->ports[i], nframes);
|
||||
|
||||
retro_assert(out);
|
||||
jack_ringbuffer_read(jd->buffer[i], (char*)out, min_avail * sizeof(jack_default_audio_sample_t));
|
||||
|
||||
for (f = min_avail; f < nframes; f++)
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#endif
|
||||
|
||||
#include <retro_miscellaneous.h>
|
||||
#include <retro_timers.h>
|
||||
|
||||
#include "../audio_driver.h"
|
||||
#include "../../verbosity.h"
|
||||
|
||||
+6
-2
@@ -120,7 +120,9 @@ static bool oss_stop(void *data)
|
||||
{
|
||||
int *fd = (int*)data;
|
||||
|
||||
ioctl(*fd, SNDCTL_DSP_RESET, 0);
|
||||
if (ioctl(*fd, SNDCTL_DSP_RESET, 0) < 0)
|
||||
return false;
|
||||
|
||||
oss_is_paused = true;
|
||||
return true;
|
||||
}
|
||||
@@ -155,7 +157,9 @@ static void oss_free(void *data)
|
||||
{
|
||||
int *fd = (int*)data;
|
||||
|
||||
ioctl(*fd, SNDCTL_DSP_RESET, 0);
|
||||
if (ioctl(*fd, SNDCTL_DSP_RESET, 0) < 0)
|
||||
return;
|
||||
|
||||
close(*fd);
|
||||
free(fd);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <rthreads/rthreads.h>
|
||||
#include <queues/fifo_queue.h>
|
||||
#include <retro_inline.h>
|
||||
#include <retro_math.h>
|
||||
|
||||
#include "../audio_driver.h"
|
||||
#include "../../verbosity.h"
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+9
-12
@@ -21,13 +21,13 @@
|
||||
#define _WIN32_WINNT 0x0600
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
|
||||
#include <Windows.h>
|
||||
#include <Winerror.h>
|
||||
#include <windows.h>
|
||||
#include <winerror.h>
|
||||
#include <initguid.h>
|
||||
#include <Mmdeviceapi.h>
|
||||
#include <Mmreg.h>
|
||||
#include <Audioclient.h>
|
||||
#include <Propidl.h>
|
||||
#include <mmdeviceapi.h>
|
||||
#include <mmreg.h>
|
||||
#include <audioclient.h>
|
||||
#include <propidl.h>
|
||||
|
||||
#include <lists/string_list.h>
|
||||
#include <queues/fifo_queue.h>
|
||||
@@ -263,14 +263,12 @@ static void wasapi_set_format(WAVEFORMATEXTENSIBLE *wf,
|
||||
static IAudioClient *wasapi_init_client_sh(IMMDevice *device,
|
||||
bool *float_fmt, unsigned *rate, unsigned latency)
|
||||
{
|
||||
HRESULT hr;
|
||||
WAVEFORMATEXTENSIBLE wf;
|
||||
int i, j;
|
||||
IAudioClient *client = NULL;
|
||||
bool float_fmt_res = *float_fmt;
|
||||
unsigned rate_res = *rate;
|
||||
|
||||
hr = device->lpVtbl->Activate(device, &IID_IAudioClient,
|
||||
HRESULT hr = device->lpVtbl->Activate(device, &IID_IAudioClient,
|
||||
CLSCTX_ALL, NULL, (void**)&client);
|
||||
WASAPI_HR_CHECK(hr, "IMMDevice::Activate", return NULL);
|
||||
|
||||
@@ -331,7 +329,6 @@ error:
|
||||
static IAudioClient *wasapi_init_client_ex(IMMDevice *device,
|
||||
bool *float_fmt, unsigned *rate, unsigned latency)
|
||||
{
|
||||
HRESULT hr;
|
||||
WAVEFORMATEXTENSIBLE wf;
|
||||
int i, j;
|
||||
IAudioClient *client = NULL;
|
||||
@@ -340,8 +337,8 @@ static IAudioClient *wasapi_init_client_ex(IMMDevice *device,
|
||||
REFERENCE_TIME minimum_period = 0;
|
||||
REFERENCE_TIME buffer_duration = 0;
|
||||
UINT32 buffer_length = 0;
|
||||
|
||||
hr = device->lpVtbl->Activate(device, &IID_IAudioClient,
|
||||
HRESULT hr = device->lpVtbl->Activate(
|
||||
device, &IID_IAudioClient,
|
||||
CLSCTX_ALL, NULL, (void**)&client);
|
||||
WASAPI_HR_CHECK(hr, "IMMDevice::Activate", return NULL);
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
#include <retro_inline.h>
|
||||
#include <retro_assert.h>
|
||||
#include <retro_miscellaneous.h>
|
||||
#include <retro_timers.h>
|
||||
|
||||
/*
|
||||
****************************************************************************
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
#include <retro_miscellaneous.h>
|
||||
#include <gfx/scaler/scaler.h>
|
||||
#include <gfx/video_frame.h>
|
||||
#include <retro_stat.h>
|
||||
#include <file/file_path.h>
|
||||
|
||||
#include <compat/strl.h>
|
||||
|
||||
|
||||
+185
-33
@@ -21,6 +21,7 @@
|
||||
#include <compat/strl.h>
|
||||
#include <rhash.h>
|
||||
#include <retro_miscellaneous.h>
|
||||
#include <retro_math.h>
|
||||
#include <net/net_http.h>
|
||||
#include <libretro.h>
|
||||
|
||||
@@ -96,7 +97,7 @@
|
||||
|
||||
#define CHEEVOS_SIX_MB ( 6 * 1024 * 1024)
|
||||
#define CHEEVOS_EIGHT_MB ( 8 * 1024 * 1024)
|
||||
#define CHEEVOS_SIZE_LIMIT (32 * 1024 * 1024)
|
||||
#define CHEEVOS_SIZE_LIMIT (64 * 1024 * 1024)
|
||||
|
||||
enum
|
||||
{
|
||||
@@ -169,6 +170,9 @@ enum
|
||||
CHEEVOS_COND_TYPE_STANDARD = 0,
|
||||
CHEEVOS_COND_TYPE_PAUSE_IF,
|
||||
CHEEVOS_COND_TYPE_RESET_IF,
|
||||
CHEEVOS_COND_TYPE_ADD_SOURCE,
|
||||
CHEEVOS_COND_TYPE_SUB_SOURCE,
|
||||
CHEEVOS_COND_TYPE_ADD_HITS,
|
||||
|
||||
CHEEVOS_COND_TYPE_LAST
|
||||
}; /* cheevos_cond_t.type */
|
||||
@@ -320,6 +324,9 @@ typedef struct
|
||||
{
|
||||
int console_id;
|
||||
bool core_supports;
|
||||
bool addrs_patched;
|
||||
int add_buffer;
|
||||
int add_hits;
|
||||
|
||||
cheevoset_t core;
|
||||
cheevoset_t unofficial;
|
||||
@@ -335,6 +342,9 @@ static cheevos_locals_t cheevos_locals =
|
||||
{
|
||||
/* console_id */ 0,
|
||||
/* core_supports */ true,
|
||||
/* addrs_patched */ false,
|
||||
/* add_buffer */ 0,
|
||||
/* add_hits */ 0,
|
||||
/* core */ {NULL, 0},
|
||||
/* unofficial */ {NULL, 0},
|
||||
/* leaderboards */ NULL,
|
||||
@@ -501,9 +511,12 @@ static void cheevos_log_cond(const cheevos_cond_t* cond)
|
||||
{
|
||||
RARCH_LOG("CHEEVOS condition %p\n", cond);
|
||||
RARCH_LOG("CHEEVOS type: %s\n",
|
||||
cond->type == CHEEVOS_COND_TYPE_STANDARD ? "standard" :
|
||||
cond->type == CHEEVOS_COND_TYPE_PAUSE_IF ? "pause" :
|
||||
cond->type == CHEEVOS_COND_TYPE_RESET_IF ? "reset" :
|
||||
cond->type == CHEEVOS_COND_TYPE_STANDARD ? "standard" :
|
||||
cond->type == CHEEVOS_COND_TYPE_PAUSE_IF ? "pause" :
|
||||
cond->type == CHEEVOS_COND_TYPE_RESET_IF ? "reset" :
|
||||
cond->type == CHEEVOS_COND_TYPE_ADD_SOURCE ? "add source" :
|
||||
cond->type == CHEEVOS_COND_TYPE_SUB_SOURCE ? "sub source" :
|
||||
cond->type == CHEEVOS_COND_TYPE_ADD_HITS ? "add hits" :
|
||||
"?"
|
||||
);
|
||||
RARCH_LOG("CHEEVOS req_hits: %u\n", cond->req_hits);
|
||||
@@ -627,6 +640,12 @@ static void cheevos_build_memaddr(const cheevos_condition_t* condition,
|
||||
cheevos_add_string(&aux, &left, "R:");
|
||||
else if (cond->type == CHEEVOS_COND_TYPE_PAUSE_IF)
|
||||
cheevos_add_string(&aux, &left, "P:");
|
||||
else if (cond->type == CHEEVOS_COND_TYPE_ADD_SOURCE)
|
||||
cheevos_add_string(&aux, &left, "A:");
|
||||
else if (cond->type == CHEEVOS_COND_TYPE_SUB_SOURCE)
|
||||
cheevos_add_string(&aux, &left, "B:");
|
||||
else if (cond->type == CHEEVOS_COND_TYPE_ADD_HITS)
|
||||
cheevos_add_string(&aux, &left, "C:");
|
||||
|
||||
cheevos_add_var(&cond->source, &aux, &left);
|
||||
|
||||
@@ -1032,9 +1051,18 @@ void cheevos_parse_guest_addr(cheevos_var_t *var, unsigned value)
|
||||
var->bank_id = -1;
|
||||
var->value = value;
|
||||
|
||||
if ( cheevos_locals.console_id == CHEEVOS_CONSOLE_NINTENDO
|
||||
&& var->value < 0x2000)
|
||||
var->value &= 0x07ff;
|
||||
switch (cheevos_locals.console_id)
|
||||
{
|
||||
case CHEEVOS_CONSOLE_NINTENDO:
|
||||
if (var->value < 0x2000)
|
||||
var->value &= 0x07ff;
|
||||
break;
|
||||
|
||||
case CHEEVOS_CONSOLE_GAMEBOY_COLOR:
|
||||
if (var->value >= 0xe000 && var->value <= 0xfdff)
|
||||
var->value -= 0x2000;
|
||||
break;
|
||||
}
|
||||
|
||||
if (system->mmaps.num_descriptors != 0)
|
||||
{
|
||||
@@ -1150,37 +1178,41 @@ static void cheevos_parse_var(cheevos_var_t *var, const char **memaddr)
|
||||
|
||||
var->value = (unsigned)strtol(str, &end, base);
|
||||
*memaddr = end;
|
||||
|
||||
switch (var->type)
|
||||
{
|
||||
case CHEEVOS_VAR_TYPE_ADDRESS:
|
||||
case CHEEVOS_VAR_TYPE_DELTA_MEM:
|
||||
cheevos_parse_guest_addr(var, var->value);
|
||||
#ifdef CHEEVOS_DUMP_ADDRS
|
||||
RARCH_LOG("CHEEVOS var %03d:%08X\n", var->bank_id + 1, var->value);
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void cheevos_parse_cond(cheevos_cond_t *cond, const char **memaddr)
|
||||
{
|
||||
const char* str = *memaddr;
|
||||
cond->type = CHEEVOS_COND_TYPE_STANDARD;
|
||||
|
||||
if (*str == 'R' && str[1] == ':')
|
||||
if (str[1] == ':')
|
||||
{
|
||||
cond->type = CHEEVOS_COND_TYPE_RESET_IF;
|
||||
str += 2;
|
||||
int skip = 2;
|
||||
|
||||
switch (*str)
|
||||
{
|
||||
case 'R':
|
||||
cond->type = CHEEVOS_COND_TYPE_RESET_IF;
|
||||
break;
|
||||
case 'P':
|
||||
cond->type = CHEEVOS_COND_TYPE_PAUSE_IF;
|
||||
break;
|
||||
case 'A':
|
||||
cond->type = CHEEVOS_COND_TYPE_ADD_SOURCE;
|
||||
break;
|
||||
case 'B':
|
||||
cond->type = CHEEVOS_COND_TYPE_SUB_SOURCE;
|
||||
break;
|
||||
case 'C':
|
||||
cond->type = CHEEVOS_COND_TYPE_ADD_HITS;
|
||||
break;
|
||||
default:
|
||||
skip = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
str += skip;
|
||||
}
|
||||
else if (*str == 'P' && str[1] == ':')
|
||||
{
|
||||
cond->type = CHEEVOS_COND_TYPE_PAUSE_IF;
|
||||
str += 2;
|
||||
}
|
||||
else
|
||||
cond->type = CHEEVOS_COND_TYPE_STANDARD;
|
||||
|
||||
cheevos_parse_var(&cond->source, &str);
|
||||
cond->op = cheevos_parse_operator(&str);
|
||||
@@ -1835,7 +1867,7 @@ static unsigned cheevos_get_var_value(cheevos_var_t *var)
|
||||
|
||||
static int cheevos_test_condition(cheevos_cond_t *cond)
|
||||
{
|
||||
unsigned sval = cheevos_get_var_value(&cond->source);
|
||||
unsigned sval = cheevos_get_var_value(&cond->source) + cheevos_locals.add_buffer;
|
||||
unsigned tval = cheevos_get_var_value(&cond->target);
|
||||
|
||||
switch (cond->op)
|
||||
@@ -1867,6 +1899,9 @@ static int cheevos_test_cond_set(const cheevos_condset_t *condset,
|
||||
const cheevos_cond_t *end = condset->conds + condset->count;
|
||||
cheevos_cond_t *cond = NULL;
|
||||
|
||||
cheevos_locals.add_buffer = 0;
|
||||
cheevos_locals.add_hits = 0;
|
||||
|
||||
/* Now, read all Pause conditions, and if any are true,
|
||||
* do not process further (retain old state). */
|
||||
|
||||
@@ -1894,6 +1929,32 @@ static int cheevos_test_cond_set(const cheevos_condset_t *condset,
|
||||
{
|
||||
if (cond->type != CHEEVOS_COND_TYPE_STANDARD)
|
||||
continue;
|
||||
|
||||
if (cond->type == CHEEVOS_COND_TYPE_ADD_SOURCE)
|
||||
{
|
||||
cheevos_locals.add_buffer += cheevos_get_var_value(&cond->source);
|
||||
set_valid = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cond->type == CHEEVOS_COND_TYPE_SUB_SOURCE)
|
||||
{
|
||||
cheevos_locals.add_buffer -= cheevos_get_var_value(&cond->source);
|
||||
set_valid = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cond->type == CHEEVOS_COND_TYPE_ADD_HITS)
|
||||
{
|
||||
if (cheevos_test_condition(cond))
|
||||
{
|
||||
cond->curr_hits++;
|
||||
*dirty_conds = 1;
|
||||
}
|
||||
|
||||
cheevos_locals.add_hits += cond->curr_hits;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cond->req_hits != 0 && cond->curr_hits >= cond->req_hits)
|
||||
continue;
|
||||
@@ -1908,13 +1969,16 @@ static int cheevos_test_cond_set(const cheevos_condset_t *condset,
|
||||
/* Process this logic, if this condition is true: */
|
||||
if (cond->req_hits == 0)
|
||||
; /* Not a hit-based requirement: ignore any additional logic! */
|
||||
else if (cond->curr_hits < cond->req_hits)
|
||||
else if ((cond->curr_hits + cheevos_locals.add_hits) < cond->req_hits)
|
||||
cond_valid = 0; /* Not entirely valid yet! */
|
||||
|
||||
if (match_any)
|
||||
break;
|
||||
}
|
||||
|
||||
cheevos_locals.add_buffer = 0;
|
||||
cheevos_locals.add_hits = 0;
|
||||
|
||||
/* Sequential or non-sequential? */
|
||||
set_valid &= cond_valid;
|
||||
}
|
||||
@@ -2586,10 +2650,64 @@ bool cheevos_toggle_hardcore_mode(void)
|
||||
return true;
|
||||
}
|
||||
|
||||
static void cheevos_patch_addresses(cheevoset_t* set)
|
||||
{
|
||||
cheevo_t* cheevo = set->cheevos;
|
||||
|
||||
for (unsigned i = set->count; i != 0; i--, cheevo++)
|
||||
{
|
||||
cheevos_condset_t* condset = cheevo->condition.condsets;
|
||||
|
||||
for (unsigned j = cheevo->condition.count; j != 0; j--, condset++)
|
||||
{
|
||||
cheevos_cond_t* cond = condset->conds;
|
||||
|
||||
for (unsigned k = condset->count; k != 0; k--, cond++)
|
||||
{
|
||||
switch (cond->source.type)
|
||||
{
|
||||
case CHEEVOS_VAR_TYPE_ADDRESS:
|
||||
case CHEEVOS_VAR_TYPE_DELTA_MEM:
|
||||
cheevos_parse_guest_addr(&cond->source, cond->source.value);
|
||||
#ifdef CHEEVOS_DUMP_ADDRS
|
||||
RARCH_LOG("CHEEVOS var %03d:%08X\n", cond->source.bank_id + 1, cond->source.value);
|
||||
#endif
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (cond->target.type)
|
||||
{
|
||||
case CHEEVOS_VAR_TYPE_ADDRESS:
|
||||
case CHEEVOS_VAR_TYPE_DELTA_MEM:
|
||||
cheevos_parse_guest_addr(&cond->target, cond->target.value);
|
||||
#ifdef CHEEVOS_DUMP_ADDRS
|
||||
RARCH_LOG("CHEEVOS var %03d:%08X\n", cond->target.bank_id + 1, cond->target.value);
|
||||
#endif
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cheevos_test(void)
|
||||
{
|
||||
settings_t *settings = config_get_ptr();
|
||||
|
||||
if (!cheevos_locals.addrs_patched)
|
||||
{
|
||||
cheevos_patch_addresses(&cheevos_locals.core);
|
||||
cheevos_patch_addresses(&cheevos_locals.unofficial);
|
||||
|
||||
cheevos_locals.addrs_patched = true;
|
||||
}
|
||||
|
||||
cheevos_test_cheevo_set(&cheevos_locals.core);
|
||||
|
||||
if (settings->bools.cheevos_test_unofficial)
|
||||
@@ -2740,6 +2858,8 @@ static int cheevos_iterate(coro_t* coro)
|
||||
};
|
||||
|
||||
CORO_ENTER()
|
||||
|
||||
cheevos_locals.addrs_patched = false;
|
||||
|
||||
SETTINGS = config_get_ptr();
|
||||
|
||||
@@ -2913,7 +3033,6 @@ static int cheevos_iterate(coro_t* coro)
|
||||
RARCH_ERR("CHEEVOS error loading achievements.\n");
|
||||
CORO_STOP();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef CHEEVOS_SAVE_JSON
|
||||
@@ -2943,6 +3062,30 @@ static int cheevos_iterate(coro_t* coro)
|
||||
* Outputs:
|
||||
*/
|
||||
CORO_GOSUB(PLAYING);
|
||||
|
||||
if(SETTINGS->bools.cheevos_verbose_enable)
|
||||
{
|
||||
const cheevo_t* cheevo = cheevos_locals.core.cheevos;
|
||||
const cheevo_t* end = cheevo + cheevos_locals.core.count;
|
||||
int number_of_unlocked = cheevos_locals.core.count;
|
||||
int mode;
|
||||
char msg[256];
|
||||
|
||||
if(SETTINGS->bools.cheevos_hardcore_mode_enable)
|
||||
mode = CHEEVOS_ACTIVE_HARDCORE;
|
||||
else
|
||||
mode = CHEEVOS_ACTIVE_SOFTCORE;
|
||||
|
||||
for(; cheevo < end; cheevo++)
|
||||
if(cheevo->active & mode)
|
||||
number_of_unlocked--;
|
||||
|
||||
snprintf(msg, sizeof(msg), "You have %d of %d achievements unlocked.",
|
||||
number_of_unlocked, cheevos_locals.core.count);
|
||||
msg[sizeof(msg) - 1] = 0;
|
||||
runloop_msg_queue_push(msg, 0, 6 * 60, false);
|
||||
}
|
||||
|
||||
CORO_STOP();
|
||||
|
||||
/**************************************************************************
|
||||
@@ -3264,7 +3407,16 @@ static int cheevos_iterate(coro_t* coro)
|
||||
free((void*)JSON);
|
||||
|
||||
if (!res)
|
||||
{
|
||||
if(SETTINGS->bools.cheevos_verbose_enable)
|
||||
{
|
||||
char msg[256];
|
||||
snprintf(msg, sizeof(msg), "RetroAchievements: logged in as \"%s\".", SETTINGS->arrays.cheevos_username);
|
||||
msg[sizeof(msg) - 1] = 0;
|
||||
runloop_msg_queue_push(msg, 0, 3 * 60, false);
|
||||
}
|
||||
CORO_RET();
|
||||
}
|
||||
}
|
||||
|
||||
runloop_msg_queue_push("Retro Achievements login error.", 0, 5 * 60, false);
|
||||
|
||||
@@ -88,6 +88,8 @@
|
||||
#define DEFAULT_NETWORK_CMD_PORT 55355
|
||||
#define STDIN_BUF_SIZE 4096
|
||||
|
||||
extern int libui_main(void);
|
||||
|
||||
struct command
|
||||
{
|
||||
#ifdef HAVE_STDIN_CMD
|
||||
@@ -930,7 +932,34 @@ static void command_event_set_volume(float gain)
|
||||
runloop_msg_queue_push(msg, 1, 180, true);
|
||||
RARCH_LOG("%s\n", msg);
|
||||
|
||||
audio_driver_set_volume_gain(db_to_gain(new_volume));
|
||||
audio_set_float(AUDIO_ACTION_VOLUME_GAIN, new_volume);
|
||||
}
|
||||
|
||||
/**
|
||||
* event_set_mixer_volume:
|
||||
* @gain : amount of gain to be applied to current volume level.
|
||||
*
|
||||
* Adjusts the current audio volume level.
|
||||
*
|
||||
**/
|
||||
static void command_event_set_mixer_volume(float gain)
|
||||
{
|
||||
char msg[128];
|
||||
settings_t *settings = config_get_ptr();
|
||||
float new_volume = settings->floats.audio_mixer_volume + gain;
|
||||
|
||||
new_volume = MAX(new_volume, -80.0f);
|
||||
new_volume = MIN(new_volume, 12.0f);
|
||||
|
||||
configuration_set_float(settings, settings->floats.audio_mixer_volume, new_volume);
|
||||
|
||||
snprintf(msg, sizeof(msg), "%s: %.1f dB",
|
||||
msg_hash_to_str(MSG_AUDIO_VOLUME),
|
||||
new_volume);
|
||||
runloop_msg_queue_push(msg, 1, 180, true);
|
||||
RARCH_LOG("%s\n", msg);
|
||||
|
||||
audio_set_float(AUDIO_ACTION_VOLUME_GAIN, new_volume);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1777,7 +1806,8 @@ bool command_event(enum event_command cmd, void *data)
|
||||
rarch_ctl(RARCH_CTL_SYSTEM_INFO_FREE, NULL);
|
||||
#endif
|
||||
core_unload_game();
|
||||
core_unload();
|
||||
if (!rarch_ctl(RARCH_CTL_IS_DUMMY_CORE, NULL))
|
||||
core_unload();
|
||||
}
|
||||
break;
|
||||
case CMD_EVENT_QUIT:
|
||||
@@ -1826,8 +1856,12 @@ bool command_event(enum event_command cmd, void *data)
|
||||
if (settings->bools.cheevos_hardcore_mode_enable)
|
||||
return false;
|
||||
#endif
|
||||
|
||||
if (settings->bools.rewind_enable)
|
||||
#ifdef HAVE_NETWORKING
|
||||
/* Only enable state manager if netplay is not underway
|
||||
TODO: Add a setting for these tweaks */
|
||||
if (settings->bools.rewind_enable
|
||||
&& !netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL))
|
||||
#endif
|
||||
state_manager_event_init((unsigned)settings->rewind_buffer_size);
|
||||
}
|
||||
break;
|
||||
@@ -1850,10 +1884,21 @@ bool command_event(enum event_command cmd, void *data)
|
||||
case CMD_EVENT_AUTOSAVE_INIT:
|
||||
command_event(CMD_EVENT_AUTOSAVE_DEINIT, NULL);
|
||||
#ifdef HAVE_THREADS
|
||||
if (autosave_init())
|
||||
runloop_set(RUNLOOP_ACTION_AUTOSAVE);
|
||||
else
|
||||
runloop_unset(RUNLOOP_ACTION_AUTOSAVE);
|
||||
{
|
||||
#ifdef HAVE_NETWORKING
|
||||
/* Only enable state manager if netplay is not underway
|
||||
TODO: Add a setting for these tweaks */
|
||||
settings_t *settings = config_get_ptr();
|
||||
if (settings->uints.autosave_interval != 0
|
||||
&& !netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL))
|
||||
#endif
|
||||
{
|
||||
if (autosave_init())
|
||||
runloop_set(RUNLOOP_ACTION_AUTOSAVE);
|
||||
else
|
||||
runloop_unset(RUNLOOP_ACTION_AUTOSAVE);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case CMD_EVENT_AUTOSAVE_STATE:
|
||||
@@ -2242,6 +2287,13 @@ bool command_event(enum event_command cmd, void *data)
|
||||
command_event(CMD_EVENT_NETPLAY_DEINIT, NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Disable rewind & sram autosave if it was enabled
|
||||
TODO: Add a setting for these tweaks */
|
||||
state_manager_event_deinit();
|
||||
#ifdef HAVE_THREADS
|
||||
autosave_deinit();
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
/* init netplay via lobby when content is loaded */
|
||||
@@ -2268,6 +2320,13 @@ bool command_event(enum event_command cmd, void *data)
|
||||
}
|
||||
|
||||
string_list_free(hostname);
|
||||
|
||||
/* Disable rewind if it was enabled
|
||||
TODO: Add a setting for these tweaks */
|
||||
state_manager_event_deinit();
|
||||
#ifdef HAVE_THREADS
|
||||
autosave_deinit();
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
/* init netplay via lobby when content is not loaded */
|
||||
@@ -2294,6 +2353,13 @@ bool command_event(enum event_command cmd, void *data)
|
||||
}
|
||||
|
||||
string_list_free(hostname);
|
||||
|
||||
/* Disable rewind if it was enabled
|
||||
TODO: Add a setting for these tweaks */
|
||||
state_manager_event_deinit();
|
||||
#ifdef HAVE_THREADS
|
||||
autosave_deinit();
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
case CMD_EVENT_NETPLAY_FLIP_PLAYERS:
|
||||
@@ -2511,6 +2577,12 @@ bool command_event(enum event_command cmd, void *data)
|
||||
case CMD_EVENT_VOLUME_DOWN:
|
||||
command_event_set_volume(-0.5f);
|
||||
break;
|
||||
case CMD_EVENT_MIXER_VOLUME_UP:
|
||||
command_event_set_mixer_volume(0.5f);
|
||||
break;
|
||||
case CMD_EVENT_MIXER_VOLUME_DOWN:
|
||||
command_event_set_mixer_volume(-0.5f);
|
||||
break;
|
||||
case CMD_EVENT_SET_FRAME_LIMIT:
|
||||
rarch_ctl(RARCH_CTL_SET_FRAME_LIMIT, NULL);
|
||||
break;
|
||||
@@ -2520,6 +2592,11 @@ bool command_event(enum event_command cmd, void *data)
|
||||
case CMD_EVENT_RESTORE_DEFAULT_SHADER_PRESET:
|
||||
command_event_restore_default_shader_preset();
|
||||
break;
|
||||
case CMD_EVENT_LIBUI_TEST:
|
||||
#if 0
|
||||
libui_main();
|
||||
#endif
|
||||
break;
|
||||
case CMD_EVENT_NONE:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -215,8 +215,11 @@ enum event_command
|
||||
CMD_EVENT_PERFCNT_REPORT_FRONTEND_LOG,
|
||||
CMD_EVENT_VOLUME_UP,
|
||||
CMD_EVENT_VOLUME_DOWN,
|
||||
CMD_EVENT_MIXER_VOLUME_UP,
|
||||
CMD_EVENT_MIXER_VOLUME_DOWN,
|
||||
CMD_EVENT_DISABLE_OVERRIDES,
|
||||
CMD_EVENT_RESTORE_DEFAULT_SHADER_PRESET
|
||||
CMD_EVENT_RESTORE_DEFAULT_SHADER_PRESET,
|
||||
CMD_EVENT_LIBUI_TEST
|
||||
};
|
||||
|
||||
bool command_set_shader(const char *arg);
|
||||
|
||||
+24
-2
@@ -20,6 +20,7 @@
|
||||
|
||||
#include <boolean.h>
|
||||
#include "gfx/video_defines.h"
|
||||
#include "input/input_driver.h"
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
@@ -219,7 +220,7 @@ static unsigned aspect_ratio_idx = ASPECT_RATIO_CORE;
|
||||
/* Save configuration file on exit. */
|
||||
static bool config_save_on_exit = true;
|
||||
|
||||
static bool show_hidden_files = true;
|
||||
static bool show_hidden_files = false;
|
||||
|
||||
static const bool overlay_hide_in_menu = true;
|
||||
|
||||
@@ -228,7 +229,15 @@ static const bool display_keyboard_overlay = false;
|
||||
#ifdef HAVE_MENU
|
||||
#include "menu/menu_driver.h"
|
||||
|
||||
static bool default_block_config_read = true;
|
||||
static bool default_block_config_read = true;
|
||||
|
||||
static bool menu_show_online_updater = true;
|
||||
|
||||
#if defined(HAVE_LAKKA) || defined(VITA)
|
||||
static bool menu_show_core_updater = false;
|
||||
#else
|
||||
static bool menu_show_core_updater = true;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_XMB
|
||||
static unsigned xmb_scale_factor = 100;
|
||||
@@ -414,6 +423,9 @@ static const float max_timing_skew = 0.05;
|
||||
/* Default audio volume in dB. (0.0 dB == unity gain). */
|
||||
static const float audio_volume = 0.0;
|
||||
|
||||
/* Default audio volume of the audio mixer in dB. (0.0 dB == unity gain). */
|
||||
static const float audio_mixer_volume = 0.0;
|
||||
|
||||
#ifdef HAVE_WASAPI
|
||||
/* WASAPI defaults */
|
||||
static const bool wasapi_exclusive_mode = true;
|
||||
@@ -572,6 +584,8 @@ static const bool ui_companion_enable = false;
|
||||
#if defined(ANDROID)
|
||||
#if defined(ANDROID_ARM)
|
||||
static char buildbot_server_url[] = "http://buildbot.libretro.com/nightly/android/latest/armeabi-v7a/";
|
||||
#elif defined(ANDROID_AARCH64)
|
||||
static char buildbot_server_url[] = "http://buildbot.libretro.com/nightly/android/latest/arm64-v8a/";
|
||||
#elif defined(ANDROID_X86)
|
||||
static char buildbot_server_url[] = "http://buildbot.libretro.com/nightly/android/latest/x86/";
|
||||
#else
|
||||
@@ -590,11 +604,19 @@ static char buildbot_server_url[] = "http://bot.libretro.com/nightly/apple/osx/x
|
||||
static char buildbot_server_url[] = "http://buildbot.libretro.com/nightly/apple/osx/ppc/latest/";
|
||||
#endif
|
||||
#elif defined(_WIN32) && !defined(_XBOX)
|
||||
#if _MSC_VER == 1600
|
||||
#if defined(__x86_64__)
|
||||
static char buildbot_server_url[] = "http://buildbot.libretro.com/nightly/windows-msvc2010/x86_64/latest/";
|
||||
#elif defined(__i386__) || defined(__i486__) || defined(__i686__) || defined(_M_IX86) || defined(_M_IA64)
|
||||
static char buildbot_server_url[] = "http://buildbot.libretro.com/nightly/windows-msvc2010/x86/latest/";
|
||||
#endif
|
||||
#else
|
||||
#if defined(__x86_64__)
|
||||
static char buildbot_server_url[] = "http://buildbot.libretro.com/nightly/win-x86_64/latest/";
|
||||
#elif defined(__i386__) || defined(__i486__) || defined(__i686__) || defined(_M_IX86) || defined(_M_IA64)
|
||||
static char buildbot_server_url[] = "http://buildbot.libretro.com/nightly/win-x86/latest/";
|
||||
#endif
|
||||
#endif
|
||||
#elif defined(__linux__)
|
||||
#if defined(__x86_64__)
|
||||
static char buildbot_server_url[] = "http://buildbot.libretro.com/nightly/linux/x86_64/latest/";
|
||||
|
||||
@@ -134,6 +134,12 @@ static const bool _alsa_supp = true;
|
||||
static const bool _alsa_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TINYALSA
|
||||
static const bool _tinyalsa_supp = true;
|
||||
#else
|
||||
static const bool _tinyalsa_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_COREAUDIO
|
||||
static const bool _coreaudio_supp = true;
|
||||
#else
|
||||
|
||||
+31
-18
@@ -22,7 +22,6 @@
|
||||
#include <file/file_path.h>
|
||||
#include <compat/strl.h>
|
||||
#include <compat/posix_string.h>
|
||||
#include <retro_stat.h>
|
||||
#include <retro_assert.h>
|
||||
#include <string/stdstring.h>
|
||||
|
||||
@@ -36,6 +35,7 @@
|
||||
#include "configuration.h"
|
||||
#include "content.h"
|
||||
#include "config.def.h"
|
||||
#include "config.features.h"
|
||||
#include "input/input_config.h"
|
||||
#include "input/input_keymaps.h"
|
||||
#include "input/input_remapping.h"
|
||||
@@ -139,6 +139,7 @@ enum audio_driver_enum
|
||||
AUDIO_OSS,
|
||||
AUDIO_ALSA,
|
||||
AUDIO_ALSATHREAD,
|
||||
AUDIO_TINYALSA,
|
||||
AUDIO_ROAR,
|
||||
AUDIO_AL,
|
||||
AUDIO_SL,
|
||||
@@ -312,6 +313,8 @@ static enum audio_driver_enum AUDIO_DEFAULT_DRIVER = AUDIO_PULSE;
|
||||
static enum audio_driver_enum AUDIO_DEFAULT_DRIVER = AUDIO_ALSATHREAD;
|
||||
#elif defined(HAVE_ALSA)
|
||||
static enum audio_driver_enum AUDIO_DEFAULT_DRIVER = AUDIO_ALSA;
|
||||
#elif defined(HAVE_TINYALSA)
|
||||
static enum audio_driver_enum AUDIO_DEFAULT_DRIVER = AUDIO_TINYALSA;
|
||||
#elif defined(HAVE_OSS)
|
||||
static enum audio_driver_enum AUDIO_DEFAULT_DRIVER = AUDIO_OSS;
|
||||
#elif defined(HAVE_JACK)
|
||||
@@ -378,12 +381,12 @@ static enum input_driver_enum INPUT_DEFAULT_DRIVER = INPUT_CTR;
|
||||
static enum input_driver_enum INPUT_DEFAULT_DRIVER = INPUT_WII;
|
||||
#elif defined(WIIU)
|
||||
static enum input_driver_enum INPUT_DEFAULT_DRIVER = INPUT_WIIU;
|
||||
#elif defined(HAVE_X11)
|
||||
static enum input_driver_enum INPUT_DEFAULT_DRIVER = INPUT_X;
|
||||
#elif defined(HAVE_UDEV)
|
||||
static enum input_driver_enum INPUT_DEFAULT_DRIVER = INPUT_UDEV;
|
||||
#elif defined(__linux__) && !defined(ANDROID)
|
||||
static enum input_driver_enum INPUT_DEFAULT_DRIVER = INPUT_LINUXRAW;
|
||||
#elif defined(HAVE_X11)
|
||||
static enum input_driver_enum INPUT_DEFAULT_DRIVER = INPUT_X;
|
||||
#elif defined(HAVE_WAYLAND)
|
||||
static enum input_driver_enum INPUT_DEFAULT_DRIVER = INPUT_WAYLAND;
|
||||
#elif defined(HAVE_COCOA) || defined(HAVE_COCOATOUCH)
|
||||
@@ -546,6 +549,8 @@ const char *config_get_default_audio(void)
|
||||
return "alsa";
|
||||
case AUDIO_ALSATHREAD:
|
||||
return "alsathread";
|
||||
case AUDIO_TINYALSA:
|
||||
return "tinyalsa";
|
||||
case AUDIO_ROAR:
|
||||
return "roar";
|
||||
case AUDIO_COREAUDIO:
|
||||
@@ -1153,6 +1158,7 @@ static struct config_bool_setting *populate_settings_bool(settings_t *settings,
|
||||
SETTING_BOOL("core_set_supports_no_game_enable", &settings->bools.set_supports_no_game_enable, true, true, false);
|
||||
SETTING_BOOL("audio_enable", &settings->bools.audio_enable, true, audio_enable, false);
|
||||
SETTING_BOOL("audio_mute_enable", audio_get_bool_ptr(AUDIO_ACTION_MUTE_ENABLE), true, false, false);
|
||||
SETTING_BOOL("audio_mixer_mute_enable", audio_get_bool_ptr(AUDIO_ACTION_MIXER_MUTE_ENABLE), true, false, false);
|
||||
SETTING_BOOL("location_allow", &settings->bools.location_allow, true, false, false);
|
||||
SETTING_BOOL("video_font_enable", &settings->bools.video_font_enable, true, font_enable, false);
|
||||
SETTING_BOOL("core_updater_auto_extract_archive", &settings->bools.network_buildbot_auto_extract_archive, true, true, false);
|
||||
@@ -1189,6 +1195,8 @@ static struct config_bool_setting *populate_settings_bool(settings_t *settings,
|
||||
SETTING_BOOL("xmb_show_images", &settings->bools.menu_xmb_show_images, true, xmb_show_images, false);
|
||||
#endif
|
||||
SETTING_BOOL("xmb_show_music", &settings->bools.menu_xmb_show_music, true, xmb_show_music, false);
|
||||
SETTING_BOOL("menu_show_online_updater", &settings->bools.menu_show_online_updater, true, menu_show_online_updater, false);
|
||||
SETTING_BOOL("menu_show_core_updater", &settings->bools.menu_show_core_updater, true, menu_show_core_updater, false);
|
||||
#ifdef HAVE_FFMPEG
|
||||
SETTING_BOOL("xmb_show_video", &settings->bools.menu_xmb_show_video, true, xmb_show_video, false);
|
||||
#endif
|
||||
@@ -1211,6 +1219,7 @@ static struct config_bool_setting *populate_settings_bool(settings_t *settings,
|
||||
SETTING_BOOL("cheevos_enable", &settings->bools.cheevos_enable, true, cheevos_enable, false);
|
||||
SETTING_BOOL("cheevos_test_unofficial", &settings->bools.cheevos_test_unofficial, true, false, false);
|
||||
SETTING_BOOL("cheevos_hardcore_mode_enable", &settings->bools.cheevos_hardcore_mode_enable, true, false, false);
|
||||
SETTING_BOOL("cheevos_verbose_enable", &settings->bools.cheevos_verbose_enable, true, false, false);
|
||||
#endif
|
||||
#ifdef HAVE_OVERLAY
|
||||
SETTING_BOOL("input_overlay_enable", &settings->bools.input_overlay_enable, true, config_overlay_enable_default(), false);
|
||||
@@ -1270,6 +1279,7 @@ static struct config_float_setting *populate_settings_float(settings_t *settings
|
||||
SETTING_FLOAT("audio_rate_control_delta", audio_get_float_ptr(AUDIO_ACTION_RATE_CONTROL_DELTA), true, rate_control_delta, false);
|
||||
SETTING_FLOAT("audio_max_timing_skew", &settings->floats.audio_max_timing_skew, true, max_timing_skew, false);
|
||||
SETTING_FLOAT("audio_volume", &settings->floats.audio_volume, true, audio_volume, false);
|
||||
SETTING_FLOAT("audio_mixer_volume", &settings->floats.audio_mixer_volume, true, audio_mixer_volume, false);
|
||||
#ifdef HAVE_OVERLAY
|
||||
SETTING_FLOAT("input_overlay_opacity", &settings->floats.input_overlay_opacity, true, 0.7f, false);
|
||||
SETTING_FLOAT("input_overlay_scale", &settings->floats.input_overlay_scale, true, 1.0f, false);
|
||||
@@ -1294,7 +1304,6 @@ static struct config_float_setting *populate_settings_float(settings_t *settings
|
||||
static struct config_uint_setting *populate_settings_uint(settings_t *settings, int *size)
|
||||
{
|
||||
unsigned count = 0;
|
||||
struct video_viewport *custom_vp = video_viewport_get_custom();
|
||||
struct config_uint_setting *tmp = (struct config_uint_setting*)malloc((*size + 1) * sizeof(struct config_uint_setting));
|
||||
|
||||
SETTING_UINT("input_bind_timeout", &settings->uints.input_bind_timeout, true, input_bind_timeout, false);
|
||||
@@ -1336,10 +1345,10 @@ static struct config_uint_setting *populate_settings_uint(settings_t *settings,
|
||||
SETTING_UINT("menu_shader_pipeline", &settings->uints.menu_xmb_shader_pipeline, true, menu_shader_pipeline, false);
|
||||
#endif
|
||||
SETTING_UINT("audio_out_rate", &settings->uints.audio_out_rate, true, out_rate, false);
|
||||
SETTING_UINT("custom_viewport_width", &custom_vp->width, false, 0 /* TODO */, false);
|
||||
SETTING_UINT("custom_viewport_height", &custom_vp->height, false, 0 /* TODO */, false);
|
||||
SETTING_UINT("custom_viewport_x", (unsigned*)&custom_vp->x, false, 0 /* TODO */, false);
|
||||
SETTING_UINT("custom_viewport_y", (unsigned*)&custom_vp->y, false, 0 /* TODO */, false);
|
||||
SETTING_UINT("custom_viewport_width", &settings->video_viewport_custom.width, false, 0 /* TODO */, false);
|
||||
SETTING_UINT("custom_viewport_height", &settings->video_viewport_custom.height, false, 0 /* TODO */, false);
|
||||
SETTING_UINT("custom_viewport_x", (unsigned*)&settings->video_viewport_custom.x, false, 0 /* TODO */, false);
|
||||
SETTING_UINT("custom_viewport_y", (unsigned*)&settings->video_viewport_custom.y, false, 0 /* TODO */, false);
|
||||
SETTING_UINT("content_history_size", &settings->uints.content_history_size, true, default_content_history_size, false);
|
||||
SETTING_UINT("video_hard_sync_frames", &settings->uints.video_hard_sync_frames, true, hard_sync_frames, false);
|
||||
SETTING_UINT("video_frame_delay", &settings->uints.video_frame_delay, true, frame_delay, false);
|
||||
@@ -1531,7 +1540,8 @@ static void config_set_defaults(void)
|
||||
|
||||
settings->uints.audio_latency = g_defaults.settings.out_latency;
|
||||
|
||||
audio_driver_set_volume_gain(db_to_gain(settings->floats.audio_volume));
|
||||
audio_set_float(AUDIO_ACTION_VOLUME_GAIN, settings->floats.audio_volume);
|
||||
audio_set_float(AUDIO_ACTION_MIXER_VOLUME_GAIN, settings->floats.audio_mixer_volume);
|
||||
|
||||
settings->rewind_buffer_size = rewind_buffer_size;
|
||||
|
||||
@@ -1582,6 +1592,7 @@ static void config_set_defaults(void)
|
||||
settings->uints.input_analog_dpad_mode[i] = ANALOG_DPAD_NONE;
|
||||
if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_LIBRETRO_DEVICE, &i))
|
||||
input_config_set_device(i, RETRO_DEVICE_JOYPAD);
|
||||
settings->uints.input_mouse_index[i] = 0;
|
||||
}
|
||||
|
||||
video_driver_reset_custom_viewport();
|
||||
@@ -1955,13 +1966,10 @@ static config_file_t *open_default_config_file(void)
|
||||
|
||||
skeleton_conf[0] = '\0';
|
||||
|
||||
#if defined(__HAIKU__)
|
||||
fill_pathname_join(skeleton_conf, "/system/settings",
|
||||
file_path_str(FILE_PATH_MAIN_CONFIG), sizeof(skeleton_conf));
|
||||
#else
|
||||
fill_pathname_join(skeleton_conf, "/etc",
|
||||
file_path_str(FILE_PATH_MAIN_CONFIG), sizeof(skeleton_conf));
|
||||
#endif
|
||||
/* Build a retroarch.cfg path from the global config directory (/etc). */
|
||||
fill_pathname_join(skeleton_conf, GLOBAL_CONFIG_DIR,
|
||||
file_path_str(FILE_PATH_MAIN_CONFIG), sizeof(skeleton_conf));
|
||||
|
||||
conf = config_file_new(skeleton_conf);
|
||||
if (conf)
|
||||
RARCH_WARN("Config: using skeleton config \"%s\" as base for a new config file.\n", skeleton_conf);
|
||||
@@ -2339,6 +2347,9 @@ static bool config_load_file(const char *path, bool set_defaults,
|
||||
snprintf(buf, sizeof(buf), "input_player%u_analog_dpad_mode", i + 1);
|
||||
CONFIG_GET_INT_BASE(conf, settings, uints.input_analog_dpad_mode[i], buf);
|
||||
|
||||
snprintf(buf, sizeof(buf), "input_player%u_mouse_index", i + 1);
|
||||
CONFIG_GET_INT_BASE(conf, settings, uints.input_mouse_index[i], buf);
|
||||
|
||||
if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_LIBRETRO_DEVICE, &i))
|
||||
{
|
||||
snprintf(buf, sizeof(buf), "input_libretro_device_p%u", i + 1);
|
||||
@@ -2440,7 +2451,8 @@ static bool config_load_file(const char *path, bool set_defaults,
|
||||
settings->uints.video_swap_interval = MAX(settings->uints.video_swap_interval, 1);
|
||||
settings->uints.video_swap_interval = MIN(settings->uints.video_swap_interval, 4);
|
||||
|
||||
audio_driver_set_volume_gain(db_to_gain(settings->floats.audio_volume));
|
||||
audio_set_float(AUDIO_ACTION_VOLUME_GAIN, settings->floats.audio_volume);
|
||||
audio_set_float(AUDIO_ACTION_MIXER_VOLUME_GAIN, settings->floats.audio_mixer_volume);
|
||||
|
||||
if (string_is_empty(settings->paths.path_content_history))
|
||||
{
|
||||
@@ -3119,7 +3131,6 @@ static void save_keybind_hat(config_file_t *conf, const char *key,
|
||||
break;
|
||||
|
||||
default:
|
||||
retro_assert(0);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -3492,6 +3503,8 @@ bool config_save_file(const char *path)
|
||||
config_set_int(conf, cfg, input_config_get_device(i));
|
||||
snprintf(cfg, sizeof(cfg), "input_player%u_analog_dpad_mode", i + 1);
|
||||
config_set_int(conf, cfg, settings->uints.input_analog_dpad_mode[i]);
|
||||
snprintf(cfg, sizeof(cfg), "input_player%u_mouse_index", i + 1);
|
||||
config_set_int(conf, cfg, settings->uints.input_mouse_index[i]);
|
||||
}
|
||||
|
||||
/* Boolean settings */
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "gfx/video_driver.h"
|
||||
#include "input/input_defines.h"
|
||||
|
||||
enum override_type
|
||||
@@ -118,6 +119,8 @@ typedef struct settings
|
||||
bool menu_show_advanced_settings;
|
||||
bool menu_throttle_framerate;
|
||||
bool menu_linear_filter;
|
||||
bool menu_show_online_updater;
|
||||
bool menu_show_core_updater;
|
||||
bool menu_xmb_shadows_enable;
|
||||
bool menu_xmb_show_settings;
|
||||
bool menu_xmb_show_images;
|
||||
@@ -155,6 +158,7 @@ typedef struct settings
|
||||
bool cheevos_enable;
|
||||
bool cheevos_test_unofficial;
|
||||
bool cheevos_hardcore_mode_enable;
|
||||
bool cheevos_verbose_enable;
|
||||
#endif
|
||||
|
||||
/* Camera */
|
||||
@@ -231,6 +235,7 @@ typedef struct settings
|
||||
|
||||
float audio_max_timing_skew;
|
||||
float audio_volume; /* dB scale. */
|
||||
float audio_mixer_volume; /* dB scale. */
|
||||
|
||||
float input_overlay_opacity;
|
||||
float input_overlay_scale;
|
||||
@@ -267,6 +272,7 @@ typedef struct settings
|
||||
|
||||
unsigned input_joypad_map[MAX_USERS];
|
||||
unsigned input_device[MAX_USERS];
|
||||
unsigned input_mouse_index[MAX_USERS];
|
||||
|
||||
unsigned input_turbo_period;
|
||||
unsigned input_turbo_duty_cycle;
|
||||
@@ -408,6 +414,8 @@ typedef struct settings
|
||||
|
||||
bool modified;
|
||||
|
||||
video_viewport_t video_viewport_custom;
|
||||
|
||||
size_t rewind_buffer_size;
|
||||
} settings_t;
|
||||
|
||||
|
||||
@@ -21,8 +21,6 @@
|
||||
#include <lists/dir_list.h>
|
||||
#include <file/archive_file.h>
|
||||
|
||||
#include <retro_stat.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
@@ -23,22 +23,22 @@
|
||||
|
||||
#include "internal_cores.h"
|
||||
|
||||
static uint16_t *frame_buf;
|
||||
static uint16_t *dummy_frame_buf;
|
||||
|
||||
void libretro_dummy_retro_init(void)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
frame_buf = (uint16_t*)calloc(320 * 240, sizeof(uint16_t));
|
||||
dummy_frame_buf = (uint16_t*)calloc(320 * 240, sizeof(uint16_t));
|
||||
for (i = 0; i < 320 * 240; i++)
|
||||
frame_buf[i] = 4 << 5;
|
||||
dummy_frame_buf[i] = 4 << 5;
|
||||
}
|
||||
|
||||
void libretro_dummy_retro_deinit(void)
|
||||
{
|
||||
if (frame_buf)
|
||||
free(frame_buf);
|
||||
frame_buf = NULL;
|
||||
if (dummy_frame_buf)
|
||||
free(dummy_frame_buf);
|
||||
dummy_frame_buf = NULL;
|
||||
}
|
||||
|
||||
unsigned libretro_dummy_retro_api_version(void)
|
||||
@@ -126,7 +126,7 @@ void libretro_dummy_retro_reset(void)
|
||||
void libretro_dummy_retro_run(void)
|
||||
{
|
||||
dummy_input_poll_cb();
|
||||
dummy_video_cb(frame_buf, 320, 240, 640);
|
||||
dummy_video_cb(dummy_frame_buf, 320, 240, 640);
|
||||
}
|
||||
|
||||
/* This should never be called, it's only used as a placeholder. */
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <net/net_socket.h>
|
||||
|
||||
#include <retro_miscellaneous.h>
|
||||
#include <retro_timers.h>
|
||||
|
||||
#include <libretro.h>
|
||||
|
||||
|
||||
+8
-2
@@ -399,7 +399,7 @@ static int database_cursor_close(libretrodb_t *db, libretrodb_cursor_t *cur)
|
||||
}
|
||||
|
||||
database_info_handle_t *database_info_dir_init(const char *dir,
|
||||
enum database_type type)
|
||||
enum database_type type, retro_task_t *task)
|
||||
{
|
||||
unsigned i;
|
||||
database_info_handle_t *db = (database_info_handle_t*)
|
||||
@@ -423,6 +423,9 @@ database_info_handle_t *database_info_dir_init(const char *dir,
|
||||
{
|
||||
const char *path = db->list->elems[i].data;
|
||||
|
||||
if (task)
|
||||
task_set_progress(task, (i / (float)db->list->size) * 100);
|
||||
|
||||
if (path_is_compressed_file(path) && !path_contains_compressed_file(path))
|
||||
{
|
||||
struct string_list *archive_list = path_is_compressed_file(path) ?
|
||||
@@ -469,7 +472,7 @@ error:
|
||||
}
|
||||
|
||||
database_info_handle_t *database_info_file_init(const char *path,
|
||||
enum database_type type)
|
||||
enum database_type type, retro_task_t *task)
|
||||
{
|
||||
union string_list_elem_attr attr;
|
||||
database_info_handle_t *db = (database_info_handle_t*)
|
||||
@@ -501,6 +504,9 @@ database_info_handle_t *database_info_file_init(const char *path,
|
||||
char new_path[PATH_MAX_LENGTH];
|
||||
size_t path_len = strlen(path);
|
||||
|
||||
if (task)
|
||||
task_set_progress(task, (i / (float)archive_list->size) * 100);
|
||||
|
||||
new_path[0] = '\0';
|
||||
|
||||
strlcpy(new_path, path, sizeof(new_path));
|
||||
|
||||
+3
-2
@@ -23,6 +23,7 @@
|
||||
|
||||
#include <file/archive_file.h>
|
||||
#include <retro_common_api.h>
|
||||
#include <queues/task_queue.h>
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
@@ -125,10 +126,10 @@ database_info_list_t *database_info_list_new(const char *rdb_path,
|
||||
void database_info_list_free(database_info_list_t *list);
|
||||
|
||||
database_info_handle_t *database_info_dir_init(const char *dir,
|
||||
enum database_type type);
|
||||
enum database_type type, retro_task_t *task);
|
||||
|
||||
database_info_handle_t *database_info_file_init(const char *path,
|
||||
enum database_type type);
|
||||
enum database_type type, retro_task_t *task);
|
||||
|
||||
void database_info_free(database_info_handle_t *handle);
|
||||
|
||||
|
||||
Vendored
-32
@@ -1,32 +0,0 @@
|
||||
/* 7zAlloc.c -- Allocation functions
|
||||
2010-10-29 : Igor Pavlov : Public domain */
|
||||
|
||||
#include "7zAlloc.h"
|
||||
|
||||
void *SzAlloc(void *p, size_t size)
|
||||
{
|
||||
(void)p;
|
||||
if (size == 0)
|
||||
return 0;
|
||||
return malloc(size);
|
||||
}
|
||||
|
||||
void SzFree(void *p, void *address)
|
||||
{
|
||||
(void)p;
|
||||
free(address);
|
||||
}
|
||||
|
||||
void *SzAllocTemp(void *p, size_t size)
|
||||
{
|
||||
(void)p;
|
||||
if (size == 0)
|
||||
return 0;
|
||||
return malloc(size);
|
||||
}
|
||||
|
||||
void SzFreeTemp(void *p, void *address)
|
||||
{
|
||||
(void)p;
|
||||
free(address);
|
||||
}
|
||||
Vendored
-23
@@ -1,23 +0,0 @@
|
||||
/* 7zAlloc.h -- Allocation functions
|
||||
2010-10-29 : Igor Pavlov : Public domain */
|
||||
|
||||
#ifndef __7Z_ALLOC_H
|
||||
#define __7Z_ALLOC_H
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void *SzAlloc(void *p, size_t size);
|
||||
void SzFree(void *p, void *address);
|
||||
|
||||
void *SzAllocTemp(void *p, size_t size);
|
||||
void SzFreeTemp(void *p, void *address);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Vendored
-12
@@ -20,18 +20,6 @@ void Buf_Init(CBuf *p);
|
||||
int Buf_Create(CBuf *p, size_t size, ISzAlloc *alloc);
|
||||
void Buf_Free(CBuf *p, ISzAlloc *alloc);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t *data;
|
||||
size_t size;
|
||||
size_t pos;
|
||||
} CDynBuf;
|
||||
|
||||
void DynBuf_Construct(CDynBuf *p);
|
||||
void DynBuf_SeekToBeg(CDynBuf *p);
|
||||
int DynBuf_Write(CDynBuf *p, const uint8_t *buf, size_t size, ISzAlloc *alloc);
|
||||
void DynBuf_Free(CDynBuf *p, ISzAlloc *alloc);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
Vendored
-47
@@ -1,47 +0,0 @@
|
||||
/* 7zBuf2.c -- uint8_t Buffer
|
||||
2008-10-04 : Igor Pavlov : Public domain */
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "7zBuf.h"
|
||||
|
||||
void DynBuf_Construct(CDynBuf *p)
|
||||
{
|
||||
p->data = 0;
|
||||
p->size = 0;
|
||||
p->pos = 0;
|
||||
}
|
||||
|
||||
void DynBuf_SeekToBeg(CDynBuf *p)
|
||||
{
|
||||
p->pos = 0;
|
||||
}
|
||||
|
||||
int DynBuf_Write(CDynBuf *p, const uint8_t *buf, size_t size, ISzAlloc *alloc)
|
||||
{
|
||||
if (size > p->size - p->pos)
|
||||
{
|
||||
size_t current_size = p->pos + size;
|
||||
size_t newSize = (current_size) + (current_size / 4);
|
||||
uint8_t *data = (uint8_t *)alloc->Alloc(alloc, newSize);
|
||||
|
||||
if (data == 0)
|
||||
return 0;
|
||||
|
||||
p->size = newSize;
|
||||
memcpy(data, p->data, p->pos);
|
||||
alloc->Free(alloc, p->data);
|
||||
p->data = data;
|
||||
}
|
||||
memcpy(p->data + p->pos, buf, size);
|
||||
p->pos += size;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DynBuf_Free(CDynBuf *p, ISzAlloc *alloc)
|
||||
{
|
||||
alloc->Free(alloc, p->data);
|
||||
p->data = 0;
|
||||
p->size = 0;
|
||||
p->pos = 0;
|
||||
}
|
||||
Vendored
-41
@@ -52,12 +52,7 @@ If MY_CPU_LE_UNALIGN is not defined, we don't know about these properties of pla
|
||||
#define GetUi16(p) (*(const uint16_t *)(p))
|
||||
#define GetUi32(p) (*(const uint32_t *)(p))
|
||||
#define GetUi64(p) (*(const uint64_t *)(p))
|
||||
#define SetUi16(p, d) *(uint16_t *)(p) = (d);
|
||||
#define SetUi32(p, d) *(uint32_t *)(p) = (d);
|
||||
#define SetUi64(p, d) *(uint64_t *)(p) = (d);
|
||||
|
||||
#else
|
||||
|
||||
#define GetUi16(p) (((const uint8_t *)(p))[0] | ((uint16_t)((const uint8_t *)(p))[1] << 8))
|
||||
|
||||
#define GetUi32(p) ( \
|
||||
@@ -67,44 +62,8 @@ If MY_CPU_LE_UNALIGN is not defined, we don't know about these properties of pla
|
||||
((uint32_t)((const uint8_t *)(p))[3] << 24))
|
||||
|
||||
#define GetUi64(p) (GetUi32(p) | ((uint64_t)GetUi32(((const uint8_t *)(p)) + 4) << 32))
|
||||
|
||||
#define SetUi16(p, d) { uint32_t _x_ = (d); \
|
||||
((uint8_t *)(p))[0] = (uint8_t)_x_; \
|
||||
((uint8_t *)(p))[1] = (uint8_t)(_x_ >> 8); }
|
||||
|
||||
#define SetUi32(p, d) { uint32_t _x_ = (d); \
|
||||
((uint8_t *)(p))[0] = (uint8_t)_x_; \
|
||||
((uint8_t *)(p))[1] = (uint8_t)(_x_ >> 8); \
|
||||
((uint8_t *)(p))[2] = (uint8_t)(_x_ >> 16); \
|
||||
((uint8_t *)(p))[3] = (uint8_t)(_x_ >> 24); }
|
||||
|
||||
#define SetUi64(p, d) { uint64_t _x64_ = (d); \
|
||||
SetUi32(p, (uint32_t)_x64_); \
|
||||
SetUi32(((uint8_t *)(p)) + 4, (uint32_t)(_x64_ >> 32)); }
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(MY_CPU_LE_UNALIGN) && defined(_WIN64) && (_MSC_VER >= 1300)
|
||||
|
||||
#pragma intrinsic(_byteswap_ulong)
|
||||
#pragma intrinsic(_byteswap_uint64)
|
||||
#define GetBe32(p) _byteswap_ulong(*(const uint32_t *)(const uint8_t *)(p))
|
||||
#define GetBe64(p) _byteswap_uint64(*(const uint64_t *)(const uint8_t *)(p))
|
||||
|
||||
#else
|
||||
|
||||
#define GetBe32(p) ( \
|
||||
((uint32_t)((const uint8_t *)(p))[0] << 24) | \
|
||||
((uint32_t)((const uint8_t *)(p))[1] << 16) | \
|
||||
((uint32_t)((const uint8_t *)(p))[2] << 8) | \
|
||||
((const uint8_t *)(p))[3] )
|
||||
|
||||
#define GetBe64(p) (((uint64_t)GetBe32(p) << 32) | GetBe32(((const uint8_t *)(p)) + 4))
|
||||
|
||||
#endif
|
||||
|
||||
#define GetBe16(p) (((uint16_t)((const uint8_t *)(p))[0] << 8) | ((const uint8_t *)(p))[1])
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
Vendored
-20
@@ -1,20 +0,0 @@
|
||||
/* RotateDefs.h -- Rotate functions
|
||||
2009-02-07 : Igor Pavlov : Public domain */
|
||||
|
||||
#ifndef __ROTATE_DEFS_H
|
||||
#define __ROTATE_DEFS_H
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#include <stdlib.h>
|
||||
#define rotlFixed(x, n) _rotl((x), (n))
|
||||
#define rotrFixed(x, n) _rotr((x), (n))
|
||||
|
||||
#else
|
||||
|
||||
#define rotlFixed(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
|
||||
#define rotrFixed(x, n) (((x) >> (n)) | ((x) << (32 - (n))))
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Vendored
-19
@@ -60,29 +60,10 @@ typedef int Bool;
|
||||
#define True 1
|
||||
#define False 0
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
#define MY_STD_CALL __stdcall
|
||||
#else
|
||||
#define MY_STD_CALL
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#if _MSC_VER >= 1300
|
||||
#define MY_NO_INLINE __declspec(noinline)
|
||||
#else
|
||||
#define MY_NO_INLINE
|
||||
#endif
|
||||
|
||||
#define MY_CDECL __cdecl
|
||||
#define MY_FAST_CALL __fastcall
|
||||
|
||||
#else
|
||||
|
||||
#define MY_CDECL
|
||||
#define MY_FAST_CALL
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
Vendored
+167
@@ -0,0 +1,167 @@
|
||||
// 29 march 2014
|
||||
#include "../ui.h"
|
||||
#include "uipriv.h"
|
||||
|
||||
/*
|
||||
Windows and GTK+ have a limit of 2 and 3 clicks, respectively, natively supported. Fortunately, we can simulate the double/triple-click behavior to build higher-order clicks. We can use the same algorithm Windows uses on both:
|
||||
http://blogs.msdn.com/b/oldnewthing/archive/2004/10/18/243925.aspx
|
||||
For GTK+, we pull the double-click time and double-click distance, which work the same as the equivalents on Windows (so the distance is in all directions), from the GtkSettings system.
|
||||
|
||||
On GTK+ this will also allow us to discard the GDK_BUTTON_2PRESS and GDK_BUTTON_3PRESS events, so the button press stream will be just like on other platforms.
|
||||
|
||||
Thanks to mclasen, garnacho_, halfline, and tristan in irc.gimp.net/#gtk+.
|
||||
*/
|
||||
|
||||
// x, y, xdist, ydist, and c.rect must have the same units
|
||||
// so must time, maxTime, and c.prevTime
|
||||
int clickCounterClick(clickCounter *c, int button, int x, int y, uintptr_t time, uintptr_t maxTime, int32_t xdist, int32_t ydist)
|
||||
{
|
||||
// different button than before? if so, don't count
|
||||
if (button != c->curButton)
|
||||
c->count = 0;
|
||||
|
||||
// (x, y) in the allowed region for a double-click? if not, don't count
|
||||
if (x < c->rectX0)
|
||||
c->count = 0;
|
||||
if (y < c->rectY0)
|
||||
c->count = 0;
|
||||
if (x >= c->rectX1)
|
||||
c->count = 0;
|
||||
if (y >= c->rectY1)
|
||||
c->count = 0;
|
||||
|
||||
// too slow? if so, don't count
|
||||
// note the below expression; time > (c.prevTime + maxTime) can overflow!
|
||||
if ((time - c->prevTime) > maxTime) // too slow; don't count
|
||||
c->count = 0;
|
||||
|
||||
c->count++; // if either of the above ifs happened, this will make the click count 1; otherwise it will make the click count 2, 3, 4, 5, ...
|
||||
|
||||
// now we need to update the internal structures for the next test
|
||||
c->curButton = button;
|
||||
c->prevTime = time;
|
||||
c->rectX0 = x - xdist;
|
||||
c->rectY0 = y - ydist;
|
||||
c->rectX1 = x + xdist;
|
||||
c->rectY1 = y + ydist;
|
||||
|
||||
return c->count;
|
||||
}
|
||||
|
||||
void clickCounterReset(clickCounter *c)
|
||||
{
|
||||
c->curButton = 0;
|
||||
c->rectX0 = 0;
|
||||
c->rectY0 = 0;
|
||||
c->rectX1 = 0;
|
||||
c->rectY1 = 0;
|
||||
c->prevTime = 0;
|
||||
c->count = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
For position independence across international keyboard layouts, typewriter keys are read using scancodes (which are always set 1).
|
||||
Windows provides the scancodes directly in the LPARAM.
|
||||
GTK+ provides the scancodes directly from the underlying window system via GdkEventKey.hardware_keycode.
|
||||
On X11, this is scancode + 8 (because X11 keyboard codes have a range of [8,255]).
|
||||
Wayland is guaranteed to give the same result (thanks ebassi in irc.gimp.net/#gtk+).
|
||||
On Linux, where evdev is used instead of polling scancodes directly from the keyboard, evdev's typewriter section key code constants are the same as scancodes anyway, so the rules above apply.
|
||||
Typewriter section scancodes are the same across international keyboards with some exceptions that have been accounted for (see KeyEvent's documentation); see http://www.quadibloc.com/comp/scan.htm for details.
|
||||
Non-typewriter keys can be handled safely using constants provided by the respective backend API.
|
||||
|
||||
Because GTK+ keysyms may or may not obey Num Lock, we also handle the 0-9 and . keys on the numeric keypad with scancodes (they match too).
|
||||
*/
|
||||
|
||||
// use uintptr_t to be safe; the size of the scancode/hardware key code field on each platform is different
|
||||
static const struct {
|
||||
uintptr_t scancode;
|
||||
char equiv;
|
||||
} scancodeKeys[] = {
|
||||
{ 0x02, '1' },
|
||||
{ 0x03, '2' },
|
||||
{ 0x04, '3' },
|
||||
{ 0x05, '4' },
|
||||
{ 0x06, '5' },
|
||||
{ 0x07, '6' },
|
||||
{ 0x08, '7' },
|
||||
{ 0x09, '8' },
|
||||
{ 0x0A, '9' },
|
||||
{ 0x0B, '0' },
|
||||
{ 0x0C, '-' },
|
||||
{ 0x0D, '=' },
|
||||
{ 0x0E, '\b' },
|
||||
{ 0x0F, '\t' },
|
||||
{ 0x10, 'q' },
|
||||
{ 0x11, 'w' },
|
||||
{ 0x12, 'e' },
|
||||
{ 0x13, 'r' },
|
||||
{ 0x14, 't' },
|
||||
{ 0x15, 'y' },
|
||||
{ 0x16, 'u' },
|
||||
{ 0x17, 'i' },
|
||||
{ 0x18, 'o' },
|
||||
{ 0x19, 'p' },
|
||||
{ 0x1A, '[' },
|
||||
{ 0x1B, ']' },
|
||||
{ 0x1C, '\n' },
|
||||
{ 0x1E, 'a' },
|
||||
{ 0x1F, 's' },
|
||||
{ 0x20, 'd' },
|
||||
{ 0x21, 'f' },
|
||||
{ 0x22, 'g' },
|
||||
{ 0x23, 'h' },
|
||||
{ 0x24, 'j' },
|
||||
{ 0x25, 'k' },
|
||||
{ 0x26, 'l' },
|
||||
{ 0x27, ';' },
|
||||
{ 0x28, '\'' },
|
||||
{ 0x29, '`' },
|
||||
{ 0x2B, '\\' },
|
||||
{ 0x2C, 'z' },
|
||||
{ 0x2D, 'x' },
|
||||
{ 0x2E, 'c' },
|
||||
{ 0x2F, 'v' },
|
||||
{ 0x30, 'b' },
|
||||
{ 0x31, 'n' },
|
||||
{ 0x32, 'm' },
|
||||
{ 0x33, ',' },
|
||||
{ 0x34, '.' },
|
||||
{ 0x35, '/' },
|
||||
{ 0x39, ' ' },
|
||||
{ 0xFFFF, 0 },
|
||||
};
|
||||
|
||||
static const struct {
|
||||
uintptr_t scancode;
|
||||
uiExtKey equiv;
|
||||
} scancodeExtKeys[] = {
|
||||
{ 0x47, uiExtKeyN7 },
|
||||
{ 0x48, uiExtKeyN8 },
|
||||
{ 0x49, uiExtKeyN9 },
|
||||
{ 0x4B, uiExtKeyN4 },
|
||||
{ 0x4C, uiExtKeyN5 },
|
||||
{ 0x4D, uiExtKeyN6 },
|
||||
{ 0x4F, uiExtKeyN1 },
|
||||
{ 0x50, uiExtKeyN2 },
|
||||
{ 0x51, uiExtKeyN3 },
|
||||
{ 0x52, uiExtKeyN0 },
|
||||
{ 0x53, uiExtKeyNDot },
|
||||
{ 0xFFFF, 0 },
|
||||
};
|
||||
|
||||
int fromScancode(uintptr_t scancode, uiAreaKeyEvent *ke)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; scancodeKeys[i].scancode != 0xFFFF; i++)
|
||||
if (scancodeKeys[i].scancode == scancode) {
|
||||
ke->Key = scancodeKeys[i].equiv;
|
||||
return 1;
|
||||
}
|
||||
for (i = 0; scancodeExtKeys[i].scancode != 0xFFFF; i++)
|
||||
if (scancodeExtKeys[i].scancode == scancode) {
|
||||
ke->ExtKey = scancodeExtKeys[i].equiv;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Vendored
+100
@@ -0,0 +1,100 @@
|
||||
// 26 may 2015
|
||||
#include "../ui.h"
|
||||
#include "uipriv.h"
|
||||
|
||||
void uiControlDestroy(uiControl *c)
|
||||
{
|
||||
(*(c->Destroy))(c);
|
||||
}
|
||||
|
||||
uintptr_t uiControlHandle(uiControl *c)
|
||||
{
|
||||
return (*(c->Handle))(c);
|
||||
}
|
||||
|
||||
uiControl *uiControlParent(uiControl *c)
|
||||
{
|
||||
return (*(c->Parent))(c);
|
||||
}
|
||||
|
||||
void uiControlSetParent(uiControl *c, uiControl *parent)
|
||||
{
|
||||
(*(c->SetParent))(c, parent);
|
||||
}
|
||||
|
||||
int uiControlToplevel(uiControl *c)
|
||||
{
|
||||
return (*(c->Toplevel))(c);
|
||||
}
|
||||
|
||||
int uiControlVisible(uiControl *c)
|
||||
{
|
||||
return (*(c->Visible))(c);
|
||||
}
|
||||
|
||||
void uiControlShow(uiControl *c)
|
||||
{
|
||||
(*(c->Show))(c);
|
||||
}
|
||||
|
||||
void uiControlHide(uiControl *c)
|
||||
{
|
||||
(*(c->Hide))(c);
|
||||
}
|
||||
|
||||
int uiControlEnabled(uiControl *c)
|
||||
{
|
||||
return (*(c->Enabled))(c);
|
||||
}
|
||||
|
||||
void uiControlEnable(uiControl *c)
|
||||
{
|
||||
(*(c->Enable))(c);
|
||||
}
|
||||
|
||||
void uiControlDisable(uiControl *c)
|
||||
{
|
||||
(*(c->Disable))(c);
|
||||
}
|
||||
|
||||
#define uiControlSignature 0x7569436F
|
||||
|
||||
uiControl *uiAllocControl(size_t size, uint32_t OSsig, uint32_t typesig, const char *typenamestr)
|
||||
{
|
||||
uiControl *c = (uiControl *) uiAlloc(size, typenamestr);
|
||||
c->Signature = uiControlSignature;
|
||||
c->OSSignature = OSsig;
|
||||
c->TypeSignature = typesig;
|
||||
return c;
|
||||
}
|
||||
|
||||
void uiFreeControl(uiControl *c)
|
||||
{
|
||||
if (uiControlParent(c) != NULL)
|
||||
userbug("You cannot destroy a uiControl while it still has a parent. (control: %p)", c);
|
||||
uiFree(c);
|
||||
}
|
||||
|
||||
void uiControlVerifySetParent(uiControl *c, uiControl *parent)
|
||||
{
|
||||
uiControl *curParent;
|
||||
|
||||
if (uiControlToplevel(c))
|
||||
userbug("You cannot give a toplevel uiControl a parent. (control: %p)", c);
|
||||
curParent = uiControlParent(c);
|
||||
if (parent != NULL && curParent != NULL)
|
||||
userbug("You cannot give a uiControl a parent while it already has one. (control: %p; current parent: %p; new parent: %p)", c, curParent, parent);
|
||||
if (parent == NULL && curParent == NULL)
|
||||
implbug("attempt to double unparent uiControl %p", c);
|
||||
}
|
||||
|
||||
int uiControlEnabledToUser(uiControl *c)
|
||||
{
|
||||
while (c != NULL)
|
||||
{
|
||||
if (!uiControlEnabled(c))
|
||||
return 0;
|
||||
c = uiControlParent(c);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
/* 24 april 2016 */
|
||||
|
||||
#define uiAreaSignature 0x41726561
|
||||
#define uiBoxSignature 0x426F784C
|
||||
#define uiButtonSignature 0x42746F6E
|
||||
#define uiCheckboxSignature 0x43686B62
|
||||
#define uiColorButtonSignature 0x436F6C42
|
||||
#define uiComboboxSignature 0x436F6D62
|
||||
#define uiDateTimePickerSignature 0x44545069
|
||||
#define uiEditableComboboxSignature 0x45644362
|
||||
#define uiEntrySignature 0x456E7472
|
||||
#define uiFontButtonSignature 0x466F6E42
|
||||
#define uiFormSignature 0x466F726D
|
||||
#define uiGridSignature 0x47726964
|
||||
#define uiGroupSignature 0x47727062
|
||||
#define uiLabelSignature 0x4C61626C
|
||||
#define uiMultilineEntrySignature 0x4D6C6E45
|
||||
#define uiProgressBarSignature 0x50426172
|
||||
#define uiRadioButtonsSignature 0x5264696F
|
||||
#define uiSeparatorSignature 0x53657061
|
||||
#define uiSliderSignature 0x536C6964
|
||||
#define uiSpinboxSignature 0x5370696E
|
||||
#define uiTabSignature 0x54616273
|
||||
#define uiTableSignature 0x5461626C
|
||||
#define uiWindowSignature 0x57696E64
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
// 13 may 2016
|
||||
#include "../ui.h"
|
||||
#include "uipriv.h"
|
||||
|
||||
void _implbug(const char *file, const char *line, const char *func, const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
realbug(file, line, func, "POSSIBLE IMPLEMENTATION BUG; CONTACT ANDLABS:\n", format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void _userbug(const char *file, const char *line, const char *func, const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
realbug(file, line, func, "You have a bug: ", format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
Vendored
+50
@@ -0,0 +1,50 @@
|
||||
/* 11 october 2015 */
|
||||
#include <math.h>
|
||||
#include "../ui.h"
|
||||
#include "uipriv.h"
|
||||
|
||||
void uiDrawMatrixSetIdentity(uiDrawMatrix *m)
|
||||
{
|
||||
m->M11 = 1;
|
||||
m->M12 = 0;
|
||||
m->M21 = 0;
|
||||
m->M22 = 1;
|
||||
m->M31 = 0;
|
||||
m->M32 = 0;
|
||||
}
|
||||
|
||||
/* The rest of this file provides basic utilities in case the platform doesn't provide any of its own for these tasks.
|
||||
* Keep these as minimal as possible. They should generally not call other fallbacks.
|
||||
|
||||
* see https://msdn.microsoft.com/en-us/library/windows/desktop/ff684171%28v=vs.85%29.aspx#skew_transform
|
||||
* TODO see if there's a way we can avoid the multiplication */
|
||||
void fallbackSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount)
|
||||
{
|
||||
uiDrawMatrix n;
|
||||
|
||||
uiDrawMatrixSetIdentity(&n);
|
||||
/* TODO explain this */
|
||||
n.M12 = tan(yamount);
|
||||
n.M21 = tan(xamount);
|
||||
n.M31 = -y * tan(xamount);
|
||||
n.M32 = -x * tan(yamount);
|
||||
uiDrawMatrixMultiply(m, &n);
|
||||
}
|
||||
|
||||
void scaleCenter(double xCenter, double yCenter, double *x, double *y)
|
||||
{
|
||||
*x = xCenter - (*x * xCenter);
|
||||
*y = yCenter - (*y * yCenter);
|
||||
}
|
||||
|
||||
/* the basic algorithm is from cairo
|
||||
* but it's the same algorithm as the transform point,
|
||||
* just without M31 and M32 taken into account, so let's just do that instead */
|
||||
void fallbackTransformSize(uiDrawMatrix *m, double *x, double *y)
|
||||
{
|
||||
uiDrawMatrix m2 = *m;
|
||||
|
||||
m2.M31 = 0;
|
||||
m2.M32 = 0;
|
||||
uiDrawMatrixTransformPoint(&m2, x, y);
|
||||
}
|
||||
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
// 9 may 2015
|
||||
#include "../ui.h"
|
||||
#include "uipriv.h"
|
||||
|
||||
static int defaultOnShouldQuit(void *data)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int (*onShouldQuit)(void *) = defaultOnShouldQuit;
|
||||
static void *onShouldQuitData;
|
||||
|
||||
void uiOnShouldQuit(int (*f)(void *), void *data)
|
||||
{
|
||||
onShouldQuit = f;
|
||||
onShouldQuitData = data;
|
||||
}
|
||||
|
||||
int shouldQuit(void)
|
||||
{
|
||||
return (*onShouldQuit)(onShouldQuitData);
|
||||
}
|
||||
Vendored
+59
@@ -0,0 +1,59 @@
|
||||
/* 6 april 2015 */
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdarg.h>
|
||||
#include "controlsigs.h"
|
||||
|
||||
extern uiInitOptions options;
|
||||
|
||||
extern void *uiAlloc(size_t, const char *);
|
||||
#define uiNew(T) ((T *) uiAlloc(sizeof (T), #T))
|
||||
extern void *uiRealloc(void *, size_t, const char *);
|
||||
extern void uiFree(void *);
|
||||
|
||||
/* ugh, this was only introduced in MSVC 2015... */
|
||||
#ifdef _MSC_VER
|
||||
#define __func__ __FUNCTION__
|
||||
#endif
|
||||
extern void realbug(const char *file, const char *line, const char *func, const char *prefix, const char *format, va_list ap);
|
||||
#define _ns2(s) #s
|
||||
#define _ns(s) _ns2(s)
|
||||
extern void _implbug(const char *file, const char *line, const char *func, const char *format, ...);
|
||||
#define implbug(...) _implbug(__FILE__, _ns(__LINE__), __func__, __VA_ARGS__)
|
||||
extern void _userbug(const char *file, const char *line, const char *func, const char *format, ...);
|
||||
#define userbug(...) _userbug(__FILE__, _ns(__LINE__), __func__, __VA_ARGS__)
|
||||
|
||||
/* control.c */
|
||||
extern uiControl *newControl(size_t size, uint32_t OSsig, uint32_t typesig, const char *typenamestr);
|
||||
|
||||
/* shouldquit.c */
|
||||
extern int shouldQuit(void);
|
||||
|
||||
/* areaevents.c */
|
||||
typedef struct clickCounter clickCounter;
|
||||
|
||||
/* you should call Reset() to zero-initialize a new instance
|
||||
* it doesn't matter that all the non-count fields are zero: the first click will fail the curButton test straightaway, so it'll return 1 and set the rest of the structure accordingly */
|
||||
struct clickCounter {
|
||||
int curButton;
|
||||
int rectX0;
|
||||
int rectY0;
|
||||
int rectX1;
|
||||
int rectY1;
|
||||
uintptr_t prevTime;
|
||||
int count;
|
||||
};
|
||||
int clickCounterClick(clickCounter *c, int button, int x, int y, uintptr_t time, uintptr_t maxTime, int32_t xdist, int32_t ydist);
|
||||
extern void clickCounterReset(clickCounter *);
|
||||
extern int fromScancode(uintptr_t, uiAreaKeyEvent *);
|
||||
|
||||
/* matrix.c */
|
||||
extern void fallbackSkew(uiDrawMatrix *, double, double, double, double);
|
||||
extern void scaleCenter(double, double, double *, double *);
|
||||
extern void fallbackTransformSize(uiDrawMatrix *, double *, double *);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
// 22 may 2016
|
||||
#include "../ui.h"
|
||||
#include "uipriv.h"
|
||||
|
||||
void uiUserBugCannotSetParentOnToplevel(const char *type)
|
||||
{
|
||||
userbug("You cannot make a %s a child of another uiControl,", type);
|
||||
}
|
||||
Vendored
+89
@@ -0,0 +1,89 @@
|
||||
// 4 december 2014
|
||||
#import <stdlib.h>
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
static NSMutableArray *allocations;
|
||||
NSMutableArray *delegates;
|
||||
|
||||
void initAlloc(void)
|
||||
{
|
||||
allocations = [NSMutableArray new];
|
||||
delegates = [NSMutableArray new];
|
||||
}
|
||||
|
||||
#define UINT8(p) ((uint8_t *) (p))
|
||||
#define PVOID(p) ((void *) (p))
|
||||
#define EXTRA (sizeof (size_t) + sizeof (const char **))
|
||||
#define DATA(p) PVOID(UINT8(p) + EXTRA)
|
||||
#define BASE(p) PVOID(UINT8(p) - EXTRA)
|
||||
#define SIZE(p) ((size_t *) (p))
|
||||
#define CCHAR(p) ((const char **) (p))
|
||||
#define TYPE(p) CCHAR(UINT8(p) + sizeof (size_t))
|
||||
|
||||
void uninitAlloc(void)
|
||||
{
|
||||
NSMutableString *str;
|
||||
NSValue *v;
|
||||
|
||||
[delegates release];
|
||||
if ([allocations count] == 0) {
|
||||
[allocations release];
|
||||
return;
|
||||
}
|
||||
str = [NSMutableString new];
|
||||
for (v in allocations) {
|
||||
void *ptr;
|
||||
|
||||
ptr = [v pointerValue];
|
||||
[str appendString:[NSString stringWithFormat:@"%p %s\n", ptr, *TYPE(ptr)]];
|
||||
}
|
||||
userbug("Some data was leaked; either you left a uiControl lying around or there's a bug in libui itself. Leaked data:\n%s", [str UTF8String]);
|
||||
[str release];
|
||||
}
|
||||
|
||||
void *uiAlloc(size_t size, const char *type)
|
||||
{
|
||||
void *out;
|
||||
|
||||
out = malloc(EXTRA + size);
|
||||
if (out == NULL) {
|
||||
fprintf(stderr, "memory exhausted in uiAlloc()\n");
|
||||
abort();
|
||||
}
|
||||
memset(DATA(out), 0, size);
|
||||
*SIZE(out) = size;
|
||||
*TYPE(out) = type;
|
||||
[allocations addObject:[NSValue valueWithPointer:out]];
|
||||
return DATA(out);
|
||||
}
|
||||
|
||||
void *uiRealloc(void *p, size_t new, const char *type)
|
||||
{
|
||||
void *out;
|
||||
size_t *s;
|
||||
|
||||
if (p == NULL)
|
||||
return uiAlloc(new, type);
|
||||
p = BASE(p);
|
||||
out = realloc(p, EXTRA + new);
|
||||
if (out == NULL) {
|
||||
fprintf(stderr, "memory exhausted in uiRealloc()\n");
|
||||
abort();
|
||||
}
|
||||
s = SIZE(out);
|
||||
if (new <= *s)
|
||||
memset(((uint8_t *) DATA(out)) + *s, 0, new - *s);
|
||||
*s = new;
|
||||
[allocations removeObject:[NSValue valueWithPointer:p]];
|
||||
[allocations addObject:[NSValue valueWithPointer:out]];
|
||||
return DATA(out);
|
||||
}
|
||||
|
||||
void uiFree(void *p)
|
||||
{
|
||||
if (p == NULL)
|
||||
implbug("attempt to uiFree(NULL)");
|
||||
p = BASE(p);
|
||||
free(p);
|
||||
[allocations removeObject:[NSValue valueWithPointer:p]];
|
||||
}
|
||||
Vendored
+475
@@ -0,0 +1,475 @@
|
||||
// 9 september 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// 10.8 fixups
|
||||
#define NSEventModifierFlags NSUInteger
|
||||
|
||||
@interface areaView : NSView {
|
||||
uiArea *libui_a;
|
||||
NSTrackingArea *libui_ta;
|
||||
NSSize libui_ss;
|
||||
BOOL libui_enabled;
|
||||
}
|
||||
- (id)initWithFrame:(NSRect)r area:(uiArea *)a;
|
||||
- (uiModifiers)parseModifiers:(NSEvent *)e;
|
||||
- (void)doMouseEvent:(NSEvent *)e;
|
||||
- (int)sendKeyEvent:(uiAreaKeyEvent *)ke;
|
||||
- (int)doKeyDownUp:(NSEvent *)e up:(int)up;
|
||||
- (int)doKeyDown:(NSEvent *)e;
|
||||
- (int)doKeyUp:(NSEvent *)e;
|
||||
- (int)doFlagsChanged:(NSEvent *)e;
|
||||
- (void)setupNewTrackingArea;
|
||||
- (void)setScrollingSize:(NSSize)s;
|
||||
- (BOOL)isEnabled;
|
||||
- (void)setEnabled:(BOOL)e;
|
||||
@end
|
||||
|
||||
struct uiArea {
|
||||
uiDarwinControl c;
|
||||
NSView *view; // either sv or area depending on whether it is scrolling
|
||||
NSScrollView *sv;
|
||||
areaView *area;
|
||||
struct scrollViewData *d;
|
||||
uiAreaHandler *ah;
|
||||
BOOL scrolling;
|
||||
NSEvent *dragevent;
|
||||
};
|
||||
|
||||
@implementation areaView
|
||||
|
||||
- (id)initWithFrame:(NSRect)r area:(uiArea *)a
|
||||
{
|
||||
self = [super initWithFrame:r];
|
||||
if (self) {
|
||||
self->libui_a = a;
|
||||
[self setupNewTrackingArea];
|
||||
self->libui_ss = r.size;
|
||||
self->libui_enabled = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)drawRect:(NSRect)r
|
||||
{
|
||||
uiArea *a = self->libui_a;
|
||||
CGContextRef c;
|
||||
uiAreaDrawParams dp;
|
||||
|
||||
c = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
|
||||
// see draw.m under text for why we need the height
|
||||
dp.Context = newContext(c, [self bounds].size.height);
|
||||
|
||||
dp.AreaWidth = 0;
|
||||
dp.AreaHeight = 0;
|
||||
if (!a->scrolling) {
|
||||
dp.AreaWidth = [self frame].size.width;
|
||||
dp.AreaHeight = [self frame].size.height;
|
||||
}
|
||||
|
||||
dp.ClipX = r.origin.x;
|
||||
dp.ClipY = r.origin.y;
|
||||
dp.ClipWidth = r.size.width;
|
||||
dp.ClipHeight = r.size.height;
|
||||
|
||||
// no need to save or restore the graphics state to reset transformations; Cocoa creates a brand-new context each time
|
||||
(*(a->ah->Draw))(a->ah, a, &dp);
|
||||
|
||||
freeContext(dp.Context);
|
||||
}
|
||||
|
||||
- (BOOL)isFlipped
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)acceptsFirstResponder
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (uiModifiers)parseModifiers:(NSEvent *)e
|
||||
{
|
||||
NSEventModifierFlags mods;
|
||||
uiModifiers m;
|
||||
|
||||
m = 0;
|
||||
mods = [e modifierFlags];
|
||||
if ((mods & NSControlKeyMask) != 0)
|
||||
m |= uiModifierCtrl;
|
||||
if ((mods & NSAlternateKeyMask) != 0)
|
||||
m |= uiModifierAlt;
|
||||
if ((mods & NSShiftKeyMask) != 0)
|
||||
m |= uiModifierShift;
|
||||
if ((mods & NSCommandKeyMask) != 0)
|
||||
m |= uiModifierSuper;
|
||||
return m;
|
||||
}
|
||||
|
||||
- (void)setupNewTrackingArea
|
||||
{
|
||||
self->libui_ta = [[NSTrackingArea alloc] initWithRect:[self bounds]
|
||||
options:(NSTrackingMouseEnteredAndExited |
|
||||
NSTrackingMouseMoved |
|
||||
NSTrackingActiveAlways |
|
||||
NSTrackingInVisibleRect |
|
||||
NSTrackingEnabledDuringMouseDrag)
|
||||
owner:self
|
||||
userInfo:nil];
|
||||
[self addTrackingArea:self->libui_ta];
|
||||
}
|
||||
|
||||
- (void)updateTrackingAreas
|
||||
{
|
||||
[self removeTrackingArea:self->libui_ta];
|
||||
[self->libui_ta release];
|
||||
[self setupNewTrackingArea];
|
||||
}
|
||||
|
||||
// capture on drag is done automatically on OS X
|
||||
- (void)doMouseEvent:(NSEvent *)e
|
||||
{
|
||||
uiArea *a = self->libui_a;
|
||||
uiAreaMouseEvent me;
|
||||
NSPoint point;
|
||||
int buttonNumber;
|
||||
NSUInteger pmb;
|
||||
unsigned int i, max;
|
||||
|
||||
// this will convert point to drawing space
|
||||
// thanks swillits in irc.freenode.net/#macdev
|
||||
point = [self convertPoint:[e locationInWindow] fromView:nil];
|
||||
me.X = point.x;
|
||||
me.Y = point.y;
|
||||
|
||||
me.AreaWidth = 0;
|
||||
me.AreaHeight = 0;
|
||||
if (!a->scrolling) {
|
||||
me.AreaWidth = [self frame].size.width;
|
||||
me.AreaHeight = [self frame].size.height;
|
||||
}
|
||||
|
||||
buttonNumber = [e buttonNumber] + 1;
|
||||
// swap button numbers 2 and 3 (right and middle)
|
||||
if (buttonNumber == 2)
|
||||
buttonNumber = 3;
|
||||
else if (buttonNumber == 3)
|
||||
buttonNumber = 2;
|
||||
|
||||
me.Down = 0;
|
||||
me.Up = 0;
|
||||
me.Count = 0;
|
||||
switch ([e type]) {
|
||||
case NSLeftMouseDown:
|
||||
case NSRightMouseDown:
|
||||
case NSOtherMouseDown:
|
||||
me.Down = buttonNumber;
|
||||
me.Count = [e clickCount];
|
||||
break;
|
||||
case NSLeftMouseUp:
|
||||
case NSRightMouseUp:
|
||||
case NSOtherMouseUp:
|
||||
me.Up = buttonNumber;
|
||||
break;
|
||||
case NSLeftMouseDragged:
|
||||
case NSRightMouseDragged:
|
||||
case NSOtherMouseDragged:
|
||||
// we include the button that triggered the dragged event in the Held fields
|
||||
buttonNumber = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
me.Modifiers = [self parseModifiers:e];
|
||||
|
||||
pmb = [NSEvent pressedMouseButtons];
|
||||
me.Held1To64 = 0;
|
||||
if (buttonNumber != 1 && (pmb & 1) != 0)
|
||||
me.Held1To64 |= 1;
|
||||
if (buttonNumber != 2 && (pmb & 4) != 0)
|
||||
me.Held1To64 |= 2;
|
||||
if (buttonNumber != 3 && (pmb & 2) != 0)
|
||||
me.Held1To64 |= 4;
|
||||
// buttons 4..32
|
||||
// https://developer.apple.com/library/mac/documentation/Carbon/Reference/QuartzEventServicesRef/index.html#//apple_ref/c/tdef/CGMouseButton says Quartz only supports up to 32 buttons
|
||||
max = 32;
|
||||
for (i = 4; i <= max; i++) {
|
||||
uint64_t j;
|
||||
|
||||
if (buttonNumber == i)
|
||||
continue;
|
||||
j = 1 << (i - 1);
|
||||
if ((pmb & j) != 0)
|
||||
me.Held1To64 |= j;
|
||||
}
|
||||
|
||||
if (self->libui_enabled) {
|
||||
// and allow dragging here
|
||||
a->dragevent = e;
|
||||
(*(a->ah->MouseEvent))(a->ah, a, &me);
|
||||
a->dragevent = nil;
|
||||
}
|
||||
}
|
||||
|
||||
#define mouseEvent(name) \
|
||||
- (void)name:(NSEvent *)e \
|
||||
{ \
|
||||
[self doMouseEvent:e]; \
|
||||
}
|
||||
mouseEvent(mouseMoved)
|
||||
mouseEvent(mouseDragged)
|
||||
mouseEvent(rightMouseDragged)
|
||||
mouseEvent(otherMouseDragged)
|
||||
mouseEvent(mouseDown)
|
||||
mouseEvent(rightMouseDown)
|
||||
mouseEvent(otherMouseDown)
|
||||
mouseEvent(mouseUp)
|
||||
mouseEvent(rightMouseUp)
|
||||
mouseEvent(otherMouseUp)
|
||||
|
||||
- (void)mouseEntered:(NSEvent *)e
|
||||
{
|
||||
uiArea *a = self->libui_a;
|
||||
|
||||
if (self->libui_enabled)
|
||||
(*(a->ah->MouseCrossed))(a->ah, a, 0);
|
||||
}
|
||||
|
||||
- (void)mouseExited:(NSEvent *)e
|
||||
{
|
||||
uiArea *a = self->libui_a;
|
||||
|
||||
if (self->libui_enabled)
|
||||
(*(a->ah->MouseCrossed))(a->ah, a, 1);
|
||||
}
|
||||
|
||||
// note: there is no equivalent to WM_CAPTURECHANGED on Mac OS X; there literally is no way to break a grab like that
|
||||
// even if I invoke the task switcher and switch processes, the mouse grab will still be held until I let go of all buttons
|
||||
// therefore, no DragBroken()
|
||||
|
||||
- (int)sendKeyEvent:(uiAreaKeyEvent *)ke
|
||||
{
|
||||
uiArea *a = self->libui_a;
|
||||
|
||||
return (*(a->ah->KeyEvent))(a->ah, a, ke);
|
||||
}
|
||||
|
||||
- (int)doKeyDownUp:(NSEvent *)e up:(int)up
|
||||
{
|
||||
uiAreaKeyEvent ke;
|
||||
|
||||
ke.Key = 0;
|
||||
ke.ExtKey = 0;
|
||||
ke.Modifier = 0;
|
||||
|
||||
ke.Modifiers = [self parseModifiers:e];
|
||||
|
||||
ke.Up = up;
|
||||
|
||||
if (!fromKeycode([e keyCode], &ke))
|
||||
return 0;
|
||||
return [self sendKeyEvent:&ke];
|
||||
}
|
||||
|
||||
- (int)doKeyDown:(NSEvent *)e
|
||||
{
|
||||
return [self doKeyDownUp:e up:0];
|
||||
}
|
||||
|
||||
- (int)doKeyUp:(NSEvent *)e
|
||||
{
|
||||
return [self doKeyDownUp:e up:1];
|
||||
}
|
||||
|
||||
- (int)doFlagsChanged:(NSEvent *)e
|
||||
{
|
||||
uiAreaKeyEvent ke;
|
||||
uiModifiers whichmod;
|
||||
|
||||
ke.Key = 0;
|
||||
ke.ExtKey = 0;
|
||||
|
||||
// Mac OS X sends this event on both key up and key down.
|
||||
// Fortunately -[e keyCode] IS valid here, so we can simply map from key code to Modifiers, get the value of [e modifierFlags], and check if the respective bit is set or not — that will give us the up/down state
|
||||
if (!keycodeModifier([e keyCode], &whichmod))
|
||||
return 0;
|
||||
ke.Modifier = whichmod;
|
||||
ke.Modifiers = [self parseModifiers:e];
|
||||
ke.Up = (ke.Modifiers & ke.Modifier) == 0;
|
||||
// and then drop the current modifier from Modifiers
|
||||
ke.Modifiers &= ~ke.Modifier;
|
||||
return [self sendKeyEvent:&ke];
|
||||
}
|
||||
|
||||
- (void)setFrameSize:(NSSize)size
|
||||
{
|
||||
uiArea *a = self->libui_a;
|
||||
|
||||
[super setFrameSize:size];
|
||||
if (!a->scrolling)
|
||||
// we must redraw everything on resize because Windows requires it
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
// TODO does this update the frame?
|
||||
- (void)setScrollingSize:(NSSize)s
|
||||
{
|
||||
self->libui_ss = s;
|
||||
[self invalidateIntrinsicContentSize];
|
||||
}
|
||||
|
||||
- (NSSize)intrinsicContentSize
|
||||
{
|
||||
if (!self->libui_a->scrolling)
|
||||
return [super intrinsicContentSize];
|
||||
return self->libui_ss;
|
||||
}
|
||||
|
||||
- (BOOL)becomeFirstResponder
|
||||
{
|
||||
return [self isEnabled];
|
||||
}
|
||||
|
||||
- (BOOL)isEnabled
|
||||
{
|
||||
return self->libui_enabled;
|
||||
}
|
||||
|
||||
- (void)setEnabled:(BOOL)e
|
||||
{
|
||||
self->libui_enabled = e;
|
||||
if (!self->libui_enabled && [self window] != nil)
|
||||
if ([[self window] firstResponder] == self)
|
||||
[[self window] makeFirstResponder:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
uiDarwinControlAllDefaultsExceptDestroy(uiArea, view)
|
||||
|
||||
static void uiAreaDestroy(uiControl *c)
|
||||
{
|
||||
uiArea *a = uiArea(c);
|
||||
|
||||
if (a->scrolling)
|
||||
scrollViewFreeData(a->sv, a->d);
|
||||
[a->area release];
|
||||
if (a->scrolling)
|
||||
[a->sv release];
|
||||
uiFreeControl(uiControl(a));
|
||||
}
|
||||
|
||||
// called by subclasses of -[NSApplication sendEvent:]
|
||||
// by default, NSApplication eats some key events
|
||||
// this prevents that from happening with uiArea
|
||||
// see http://stackoverflow.com/questions/24099063/how-do-i-detect-keyup-in-my-nsview-with-the-command-key-held and http://lists.apple.com/archives/cocoa-dev/2003/Oct/msg00442.html
|
||||
int sendAreaEvents(NSEvent *e)
|
||||
{
|
||||
NSEventType type;
|
||||
id focused;
|
||||
areaView *view;
|
||||
|
||||
type = [e type];
|
||||
if (type != NSKeyDown && type != NSKeyUp && type != NSFlagsChanged)
|
||||
return 0;
|
||||
focused = [[e window] firstResponder];
|
||||
if (focused == nil)
|
||||
return 0;
|
||||
if (![focused isKindOfClass:[areaView class]])
|
||||
return 0;
|
||||
view = (areaView *) focused;
|
||||
switch (type) {
|
||||
case NSKeyDown:
|
||||
return [view doKeyDown:e];
|
||||
case NSKeyUp:
|
||||
return [view doKeyUp:e];
|
||||
case NSFlagsChanged:
|
||||
return [view doFlagsChanged:e];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void uiAreaSetSize(uiArea *a, int width, int height)
|
||||
{
|
||||
if (!a->scrolling)
|
||||
userbug("You cannot call uiAreaSetSize() on a non-scrolling uiArea. (area: %p)", a);
|
||||
[a->area setScrollingSize:NSMakeSize(width, height)];
|
||||
}
|
||||
|
||||
void uiAreaQueueRedrawAll(uiArea *a)
|
||||
{
|
||||
[a->area setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
void uiAreaScrollTo(uiArea *a, double x, double y, double width, double height)
|
||||
{
|
||||
if (!a->scrolling)
|
||||
userbug("You cannot call uiAreaScrollTo() on a non-scrolling uiArea. (area: %p)", a);
|
||||
[a->area scrollRectToVisible:NSMakeRect(x, y, width, height)];
|
||||
// don't worry about the return value; it just says whether scrolling was needed
|
||||
}
|
||||
|
||||
void uiAreaBeginUserWindowMove(uiArea *a)
|
||||
{
|
||||
libuiNSWindow *w;
|
||||
|
||||
w = (libuiNSWindow *) [a->area window];
|
||||
if (w == nil)
|
||||
return; // TODO
|
||||
if (a->dragevent == nil)
|
||||
return; // TODO
|
||||
[w libui_doMove:a->dragevent];
|
||||
}
|
||||
|
||||
void uiAreaBeginUserWindowResize(uiArea *a, uiWindowResizeEdge edge)
|
||||
{
|
||||
libuiNSWindow *w;
|
||||
|
||||
w = (libuiNSWindow *) [a->area window];
|
||||
if (w == nil)
|
||||
return; // TODO
|
||||
if (a->dragevent == nil)
|
||||
return; // TODO
|
||||
[w libui_doResize:a->dragevent on:edge];
|
||||
}
|
||||
|
||||
uiArea *uiNewArea(uiAreaHandler *ah)
|
||||
{
|
||||
uiArea *a;
|
||||
|
||||
uiDarwinNewControl(uiArea, a);
|
||||
|
||||
a->ah = ah;
|
||||
a->scrolling = NO;
|
||||
|
||||
a->area = [[areaView alloc] initWithFrame:NSZeroRect area:a];
|
||||
|
||||
a->view = a->area;
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
uiArea *uiNewScrollingArea(uiAreaHandler *ah, int width, int height)
|
||||
{
|
||||
uiArea *a;
|
||||
struct scrollViewCreateParams p;
|
||||
|
||||
uiDarwinNewControl(uiArea, a);
|
||||
|
||||
a->ah = ah;
|
||||
a->scrolling = YES;
|
||||
|
||||
a->area = [[areaView alloc] initWithFrame:NSMakeRect(0, 0, width, height)
|
||||
area:a];
|
||||
|
||||
memset(&p, 0, sizeof (struct scrollViewCreateParams));
|
||||
p.DocumentView = a->area;
|
||||
p.BackgroundColor = [NSColor controlColor];
|
||||
p.DrawsBackground = 1;
|
||||
p.Bordered = NO;
|
||||
p.HScroll = YES;
|
||||
p.VScroll = YES;
|
||||
a->sv = mkScrollView(&p, &(a->d));
|
||||
|
||||
a->view = a->sv;
|
||||
|
||||
return a;
|
||||
}
|
||||
Vendored
+159
@@ -0,0 +1,159 @@
|
||||
// 30 march 2014
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
/*
|
||||
Mac OS X uses its own set of hardware key codes that are different from PC keyboard scancodes, but are positional (like PC keyboard scancodes). These are defined in <HIToolbox/Events.h>, a Carbon header. As far as I can tell, there's no way to include this header without either using an absolute path or linking Carbon into the program, so the constant values are used here instead.
|
||||
|
||||
The Cocoa docs do guarantee that -[NSEvent keyCode] results in key codes that are the same as those returned by Carbon; that is, these codes.
|
||||
*/
|
||||
|
||||
// use uintptr_t to be safe
|
||||
static const struct {
|
||||
uintptr_t keycode;
|
||||
char equiv;
|
||||
} keycodeKeys[] = {
|
||||
{ 0x00, 'a' },
|
||||
{ 0x01, 's' },
|
||||
{ 0x02, 'd' },
|
||||
{ 0x03, 'f' },
|
||||
{ 0x04, 'h' },
|
||||
{ 0x05, 'g' },
|
||||
{ 0x06, 'z' },
|
||||
{ 0x07, 'x' },
|
||||
{ 0x08, 'c' },
|
||||
{ 0x09, 'v' },
|
||||
{ 0x0B, 'b' },
|
||||
{ 0x0C, 'q' },
|
||||
{ 0x0D, 'w' },
|
||||
{ 0x0E, 'e' },
|
||||
{ 0x0F, 'r' },
|
||||
{ 0x10, 'y' },
|
||||
{ 0x11, 't' },
|
||||
{ 0x12, '1' },
|
||||
{ 0x13, '2' },
|
||||
{ 0x14, '3' },
|
||||
{ 0x15, '4' },
|
||||
{ 0x16, '6' },
|
||||
{ 0x17, '5' },
|
||||
{ 0x18, '=' },
|
||||
{ 0x19, '9' },
|
||||
{ 0x1A, '7' },
|
||||
{ 0x1B, '-' },
|
||||
{ 0x1C, '8' },
|
||||
{ 0x1D, '0' },
|
||||
{ 0x1E, ']' },
|
||||
{ 0x1F, 'o' },
|
||||
{ 0x20, 'u' },
|
||||
{ 0x21, '[' },
|
||||
{ 0x22, 'i' },
|
||||
{ 0x23, 'p' },
|
||||
{ 0x25, 'l' },
|
||||
{ 0x26, 'j' },
|
||||
{ 0x27, '\'' },
|
||||
{ 0x28, 'k' },
|
||||
{ 0x29, ';' },
|
||||
{ 0x2A, '\\' },
|
||||
{ 0x2B, ',' },
|
||||
{ 0x2C, '/' },
|
||||
{ 0x2D, 'n' },
|
||||
{ 0x2E, 'm' },
|
||||
{ 0x2F, '.' },
|
||||
{ 0x32, '`' },
|
||||
{ 0x24, '\n' },
|
||||
{ 0x30, '\t' },
|
||||
{ 0x31, ' ' },
|
||||
{ 0x33, '\b' },
|
||||
{ 0xFFFF, 0 },
|
||||
};
|
||||
|
||||
static const struct {
|
||||
uintptr_t keycode;
|
||||
uiExtKey equiv;
|
||||
} keycodeExtKeys[] = {
|
||||
{ 0x41, uiExtKeyNDot },
|
||||
{ 0x43, uiExtKeyNMultiply },
|
||||
{ 0x45, uiExtKeyNAdd },
|
||||
{ 0x4B, uiExtKeyNDivide },
|
||||
{ 0x4C, uiExtKeyNEnter },
|
||||
{ 0x4E, uiExtKeyNSubtract },
|
||||
{ 0x52, uiExtKeyN0 },
|
||||
{ 0x53, uiExtKeyN1 },
|
||||
{ 0x54, uiExtKeyN2 },
|
||||
{ 0x55, uiExtKeyN3 },
|
||||
{ 0x56, uiExtKeyN4 },
|
||||
{ 0x57, uiExtKeyN5 },
|
||||
{ 0x58, uiExtKeyN6 },
|
||||
{ 0x59, uiExtKeyN7 },
|
||||
{ 0x5B, uiExtKeyN8 },
|
||||
{ 0x5C, uiExtKeyN9 },
|
||||
{ 0x35, uiExtKeyEscape },
|
||||
{ 0x60, uiExtKeyF5 },
|
||||
{ 0x61, uiExtKeyF6 },
|
||||
{ 0x62, uiExtKeyF7 },
|
||||
{ 0x63, uiExtKeyF3 },
|
||||
{ 0x64, uiExtKeyF8 },
|
||||
{ 0x65, uiExtKeyF9 },
|
||||
{ 0x67, uiExtKeyF11 },
|
||||
{ 0x6D, uiExtKeyF10 },
|
||||
{ 0x6F, uiExtKeyF12 },
|
||||
{ 0x72, uiExtKeyInsert }, // listed as the Help key but it's in the same position on an Apple keyboard as the Insert key on a Windows keyboard; thanks to SeanieB from irc.badnik.net and Psy in irc.freenode.net/#macdev for confirming they have the same code
|
||||
{ 0x73, uiExtKeyHome },
|
||||
{ 0x74, uiExtKeyPageUp },
|
||||
{ 0x75, uiExtKeyDelete },
|
||||
{ 0x76, uiExtKeyF4 },
|
||||
{ 0x77, uiExtKeyEnd },
|
||||
{ 0x78, uiExtKeyF2 },
|
||||
{ 0x79, uiExtKeyPageDown },
|
||||
{ 0x7A, uiExtKeyF1 },
|
||||
{ 0x7B, uiExtKeyLeft },
|
||||
{ 0x7C, uiExtKeyRight },
|
||||
{ 0x7D, uiExtKeyDown },
|
||||
{ 0x7E, uiExtKeyUp },
|
||||
{ 0xFFFF, 0 },
|
||||
};
|
||||
|
||||
static const struct {
|
||||
uintptr_t keycode;
|
||||
uiModifiers equiv;
|
||||
} keycodeModifiers[] = {
|
||||
{ 0x37, uiModifierSuper }, // left command
|
||||
{ 0x38, uiModifierShift }, // left shift
|
||||
{ 0x3A, uiModifierAlt }, // left option
|
||||
{ 0x3B, uiModifierCtrl }, // left control
|
||||
{ 0x3C, uiModifierShift }, // right shift
|
||||
{ 0x3D, uiModifierAlt }, // right alt
|
||||
{ 0x3E, uiModifierCtrl }, // right control
|
||||
// the following is not in Events.h for some reason
|
||||
// thanks to Nicole and jedivulcan from irc.badnik.net
|
||||
{ 0x36, uiModifierSuper }, // right command
|
||||
{ 0xFFFF, 0 },
|
||||
};
|
||||
|
||||
BOOL fromKeycode(unsigned short keycode, uiAreaKeyEvent *ke)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; keycodeKeys[i].keycode != 0xFFFF; i++)
|
||||
if (keycodeKeys[i].keycode == keycode) {
|
||||
ke->Key = keycodeKeys[i].equiv;
|
||||
return YES;
|
||||
}
|
||||
for (i = 0; keycodeExtKeys[i].keycode != 0xFFFF; i++)
|
||||
if (keycodeExtKeys[i].keycode == keycode) {
|
||||
ke->ExtKey = keycodeExtKeys[i].equiv;
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
BOOL keycodeModifier(unsigned short keycode, uiModifiers *mod)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; keycodeModifiers[i].keycode != 0xFFFF; i++)
|
||||
if (keycodeModifiers[i].keycode == keycode) {
|
||||
*mod = keycodeModifiers[i].equiv;
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
Vendored
+161
@@ -0,0 +1,161 @@
|
||||
// 15 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
NSLayoutConstraint *mkConstraint(id view1, NSLayoutAttribute attr1, NSLayoutRelation relation, id view2, NSLayoutAttribute attr2, CGFloat multiplier, CGFloat c, NSString *desc)
|
||||
{
|
||||
NSLayoutConstraint *constraint;
|
||||
|
||||
constraint = [NSLayoutConstraint constraintWithItem:view1
|
||||
attribute:attr1
|
||||
relatedBy:relation
|
||||
toItem:view2
|
||||
attribute:attr2
|
||||
multiplier:multiplier
|
||||
constant:c];
|
||||
// apparently only added in 10.9
|
||||
if ([constraint respondsToSelector:@selector(setIdentifier:)])
|
||||
[((id) constraint) setIdentifier:desc];
|
||||
return constraint;
|
||||
}
|
||||
|
||||
CGFloat uiDarwinMarginAmount(void *reserved)
|
||||
{
|
||||
return 20.0;
|
||||
}
|
||||
|
||||
CGFloat uiDarwinPaddingAmount(void *reserved)
|
||||
{
|
||||
return 8.0;
|
||||
}
|
||||
|
||||
// this is needed for NSSplitView to work properly; see http://stackoverflow.com/questions/34574478/how-can-i-set-the-position-of-a-nssplitview-nowadays-setpositionofdivideratind (stal in irc.freenode.net/#macdev came up with the exact combination)
|
||||
// turns out it also works on NSTabView and NSBox too, possibly others!
|
||||
// and for bonus points, it even seems to fix unsatisfiable-constraint-autoresizing-mask issues with NSTabView and NSBox too!!! this is nuts
|
||||
void jiggleViewLayout(NSView *view)
|
||||
{
|
||||
[view setNeedsLayout:YES];
|
||||
[view layoutSubtreeIfNeeded];
|
||||
}
|
||||
|
||||
static CGFloat margins(int margined)
|
||||
{
|
||||
if (!margined)
|
||||
return 0.0;
|
||||
return uiDarwinMarginAmount(NULL);
|
||||
}
|
||||
|
||||
void singleChildConstraintsEstablish(struct singleChildConstraints *c, NSView *contentView, NSView *childView, BOOL hugsTrailing, BOOL hugsBottom, int margined, NSString *desc)
|
||||
{
|
||||
CGFloat margin;
|
||||
|
||||
margin = margins(margined);
|
||||
|
||||
c->leadingConstraint = mkConstraint(contentView, NSLayoutAttributeLeading,
|
||||
NSLayoutRelationEqual,
|
||||
childView, NSLayoutAttributeLeading,
|
||||
1, -margin,
|
||||
[desc stringByAppendingString:@" leading constraint"]);
|
||||
[contentView addConstraint:c->leadingConstraint];
|
||||
[c->leadingConstraint retain];
|
||||
|
||||
c->topConstraint = mkConstraint(contentView, NSLayoutAttributeTop,
|
||||
NSLayoutRelationEqual,
|
||||
childView, NSLayoutAttributeTop,
|
||||
1, -margin,
|
||||
[desc stringByAppendingString:@" top constraint"]);
|
||||
[contentView addConstraint:c->topConstraint];
|
||||
[c->topConstraint retain];
|
||||
|
||||
c->trailingConstraintGreater = mkConstraint(contentView, NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationGreaterThanOrEqual,
|
||||
childView, NSLayoutAttributeTrailing,
|
||||
1, margin,
|
||||
[desc stringByAppendingString:@" trailing >= constraint"]);
|
||||
if (hugsTrailing)
|
||||
[c->trailingConstraintGreater setPriority:NSLayoutPriorityDefaultLow];
|
||||
[contentView addConstraint:c->trailingConstraintGreater];
|
||||
[c->trailingConstraintGreater retain];
|
||||
|
||||
c->trailingConstraintEqual = mkConstraint(contentView, NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationEqual,
|
||||
childView, NSLayoutAttributeTrailing,
|
||||
1, margin,
|
||||
[desc stringByAppendingString:@" trailing == constraint"]);
|
||||
if (!hugsTrailing)
|
||||
[c->trailingConstraintEqual setPriority:NSLayoutPriorityDefaultLow];
|
||||
[contentView addConstraint:c->trailingConstraintEqual];
|
||||
[c->trailingConstraintEqual retain];
|
||||
|
||||
c->bottomConstraintGreater = mkConstraint(contentView, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationGreaterThanOrEqual,
|
||||
childView, NSLayoutAttributeBottom,
|
||||
1, margin,
|
||||
[desc stringByAppendingString:@" bottom >= constraint"]);
|
||||
if (hugsBottom)
|
||||
[c->bottomConstraintGreater setPriority:NSLayoutPriorityDefaultLow];
|
||||
[contentView addConstraint:c->bottomConstraintGreater];
|
||||
[c->bottomConstraintGreater retain];
|
||||
|
||||
c->bottomConstraintEqual = mkConstraint(contentView, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationEqual,
|
||||
childView, NSLayoutAttributeBottom,
|
||||
1, margin,
|
||||
[desc stringByAppendingString:@" bottom == constraint"]);
|
||||
if (!hugsBottom)
|
||||
[c->bottomConstraintEqual setPriority:NSLayoutPriorityDefaultLow];
|
||||
[contentView addConstraint:c->bottomConstraintEqual];
|
||||
[c->bottomConstraintEqual retain];
|
||||
}
|
||||
|
||||
void singleChildConstraintsRemove(struct singleChildConstraints *c, NSView *cv)
|
||||
{
|
||||
if (c->leadingConstraint != nil) {
|
||||
[cv removeConstraint:c->leadingConstraint];
|
||||
[c->leadingConstraint release];
|
||||
c->leadingConstraint = nil;
|
||||
}
|
||||
if (c->topConstraint != nil) {
|
||||
[cv removeConstraint:c->topConstraint];
|
||||
[c->topConstraint release];
|
||||
c->topConstraint = nil;
|
||||
}
|
||||
if (c->trailingConstraintGreater != nil) {
|
||||
[cv removeConstraint:c->trailingConstraintGreater];
|
||||
[c->trailingConstraintGreater release];
|
||||
c->trailingConstraintGreater = nil;
|
||||
}
|
||||
if (c->trailingConstraintEqual != nil) {
|
||||
[cv removeConstraint:c->trailingConstraintEqual];
|
||||
[c->trailingConstraintEqual release];
|
||||
c->trailingConstraintEqual = nil;
|
||||
}
|
||||
if (c->bottomConstraintGreater != nil) {
|
||||
[cv removeConstraint:c->bottomConstraintGreater];
|
||||
[c->bottomConstraintGreater release];
|
||||
c->bottomConstraintGreater = nil;
|
||||
}
|
||||
if (c->bottomConstraintEqual != nil) {
|
||||
[cv removeConstraint:c->bottomConstraintEqual];
|
||||
[c->bottomConstraintEqual release];
|
||||
c->bottomConstraintEqual = nil;
|
||||
}
|
||||
}
|
||||
|
||||
void singleChildConstraintsSetMargined(struct singleChildConstraints *c, int margined)
|
||||
{
|
||||
CGFloat margin;
|
||||
|
||||
margin = margins(margined);
|
||||
if (c->leadingConstraint != nil)
|
||||
[c->leadingConstraint setConstant:-margin];
|
||||
if (c->topConstraint != nil)
|
||||
[c->topConstraint setConstant:-margin];
|
||||
if (c->trailingConstraintGreater != nil)
|
||||
[c->trailingConstraintGreater setConstant:margin];
|
||||
if (c->trailingConstraintEqual != nil)
|
||||
[c->trailingConstraintEqual setConstant:margin];
|
||||
if (c->bottomConstraintGreater != nil)
|
||||
[c->bottomConstraintGreater setConstant:margin];
|
||||
if (c->bottomConstraintEqual != nil)
|
||||
[c->bottomConstraintEqual setConstant:margin];
|
||||
}
|
||||
Vendored
+469
@@ -0,0 +1,469 @@
|
||||
// 15 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// TODO hiding all stretchy controls still hugs trailing edge
|
||||
|
||||
@interface boxChild : NSObject
|
||||
@property uiControl *c;
|
||||
@property BOOL stretchy;
|
||||
@property NSLayoutPriority oldPrimaryHuggingPri;
|
||||
@property NSLayoutPriority oldSecondaryHuggingPri;
|
||||
- (NSView *)view;
|
||||
@end
|
||||
|
||||
@interface boxView : NSView {
|
||||
uiBox *b;
|
||||
NSMutableArray *children;
|
||||
BOOL vertical;
|
||||
int padded;
|
||||
|
||||
NSLayoutConstraint *first;
|
||||
NSMutableArray *inBetweens;
|
||||
NSLayoutConstraint *last;
|
||||
NSMutableArray *otherConstraints;
|
||||
|
||||
NSLayoutAttribute primaryStart;
|
||||
NSLayoutAttribute primaryEnd;
|
||||
NSLayoutAttribute secondaryStart;
|
||||
NSLayoutAttribute secondaryEnd;
|
||||
NSLayoutAttribute primarySize;
|
||||
NSLayoutConstraintOrientation primaryOrientation;
|
||||
NSLayoutConstraintOrientation secondaryOrientation;
|
||||
}
|
||||
- (id)initWithVertical:(BOOL)vert b:(uiBox *)bb;
|
||||
- (void)onDestroy;
|
||||
- (void)removeOurConstraints;
|
||||
- (void)syncEnableStates:(int)enabled;
|
||||
- (CGFloat)paddingAmount;
|
||||
- (void)establishOurConstraints;
|
||||
- (void)append:(uiControl *)c stretchy:(int)stretchy;
|
||||
- (void)delete:(int)n;
|
||||
- (int)isPadded;
|
||||
- (void)setPadded:(int)p;
|
||||
- (BOOL)hugsTrailing;
|
||||
- (BOOL)hugsBottom;
|
||||
- (int)nStretchy;
|
||||
@end
|
||||
|
||||
struct uiBox {
|
||||
uiDarwinControl c;
|
||||
boxView *view;
|
||||
};
|
||||
|
||||
@implementation boxChild
|
||||
|
||||
- (NSView *)view
|
||||
{
|
||||
return (NSView *) uiControlHandle(self.c);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation boxView
|
||||
|
||||
- (id)initWithVertical:(BOOL)vert b:(uiBox *)bb
|
||||
{
|
||||
self = [super initWithFrame:NSZeroRect];
|
||||
if (self != nil) {
|
||||
// the weird names vert and bb are to shut the compiler up about shadowing because implicit this/self is stupid
|
||||
self->b = bb;
|
||||
self->vertical = vert;
|
||||
self->padded = 0;
|
||||
self->children = [NSMutableArray new];
|
||||
|
||||
self->inBetweens = [NSMutableArray new];
|
||||
self->otherConstraints = [NSMutableArray new];
|
||||
|
||||
if (self->vertical) {
|
||||
self->primaryStart = NSLayoutAttributeTop;
|
||||
self->primaryEnd = NSLayoutAttributeBottom;
|
||||
self->secondaryStart = NSLayoutAttributeLeading;
|
||||
self->secondaryEnd = NSLayoutAttributeTrailing;
|
||||
self->primarySize = NSLayoutAttributeHeight;
|
||||
self->primaryOrientation = NSLayoutConstraintOrientationVertical;
|
||||
self->secondaryOrientation = NSLayoutConstraintOrientationHorizontal;
|
||||
} else {
|
||||
self->primaryStart = NSLayoutAttributeLeading;
|
||||
self->primaryEnd = NSLayoutAttributeTrailing;
|
||||
self->secondaryStart = NSLayoutAttributeTop;
|
||||
self->secondaryEnd = NSLayoutAttributeBottom;
|
||||
self->primarySize = NSLayoutAttributeWidth;
|
||||
self->primaryOrientation = NSLayoutConstraintOrientationHorizontal;
|
||||
self->secondaryOrientation = NSLayoutConstraintOrientationVertical;
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)onDestroy
|
||||
{
|
||||
boxChild *bc;
|
||||
|
||||
[self removeOurConstraints];
|
||||
[self->inBetweens release];
|
||||
[self->otherConstraints release];
|
||||
|
||||
for (bc in self->children) {
|
||||
uiControlSetParent(bc.c, NULL);
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(bc.c), nil);
|
||||
uiControlDestroy(bc.c);
|
||||
}
|
||||
[self->children release];
|
||||
}
|
||||
|
||||
- (void)removeOurConstraints
|
||||
{
|
||||
if (self->first != nil) {
|
||||
[self removeConstraint:self->first];
|
||||
[self->first release];
|
||||
self->first = nil;
|
||||
}
|
||||
if ([self->inBetweens count] != 0) {
|
||||
[self removeConstraints:self->inBetweens];
|
||||
[self->inBetweens removeAllObjects];
|
||||
}
|
||||
if (self->last != nil) {
|
||||
[self removeConstraint:self->last];
|
||||
[self->last release];
|
||||
self->last = nil;
|
||||
}
|
||||
if ([self->otherConstraints count] != 0) {
|
||||
[self removeConstraints:self->otherConstraints];
|
||||
[self->otherConstraints removeAllObjects];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)syncEnableStates:(int)enabled
|
||||
{
|
||||
boxChild *bc;
|
||||
|
||||
for (bc in self->children)
|
||||
uiDarwinControlSyncEnableState(uiDarwinControl(bc.c), enabled);
|
||||
}
|
||||
|
||||
- (CGFloat)paddingAmount
|
||||
{
|
||||
if (!self->padded)
|
||||
return 0.0;
|
||||
return uiDarwinPaddingAmount(NULL);
|
||||
}
|
||||
|
||||
- (void)establishOurConstraints
|
||||
{
|
||||
boxChild *bc;
|
||||
CGFloat padding;
|
||||
NSView *prev;
|
||||
NSLayoutConstraint *c;
|
||||
BOOL (*hugsSecondary)(uiDarwinControl *);
|
||||
|
||||
[self removeOurConstraints];
|
||||
if ([self->children count] == 0)
|
||||
return;
|
||||
padding = [self paddingAmount];
|
||||
|
||||
// first arrange in the primary direction
|
||||
prev = nil;
|
||||
for (bc in self->children) {
|
||||
if (!uiControlVisible(bc.c))
|
||||
continue;
|
||||
if (prev == nil) { // first view
|
||||
self->first = mkConstraint(self, self->primaryStart,
|
||||
NSLayoutRelationEqual,
|
||||
[bc view], self->primaryStart,
|
||||
1, 0,
|
||||
@"uiBox first primary constraint");
|
||||
[self addConstraint:self->first];
|
||||
[self->first retain];
|
||||
prev = [bc view];
|
||||
continue;
|
||||
}
|
||||
// not the first; link it
|
||||
c = mkConstraint(prev, self->primaryEnd,
|
||||
NSLayoutRelationEqual,
|
||||
[bc view], self->primaryStart,
|
||||
1, -padding,
|
||||
@"uiBox in-between primary constraint");
|
||||
[self addConstraint:c];
|
||||
[self->inBetweens addObject:c];
|
||||
prev = [bc view];
|
||||
}
|
||||
if (prev == nil) // no control visible; act as if no controls
|
||||
return;
|
||||
self->last = mkConstraint(prev, self->primaryEnd,
|
||||
NSLayoutRelationEqual,
|
||||
self, self->primaryEnd,
|
||||
1, 0,
|
||||
@"uiBox last primary constraint");
|
||||
[self addConstraint:self->last];
|
||||
[self->last retain];
|
||||
|
||||
// then arrange in the secondary direction
|
||||
hugsSecondary = uiDarwinControlHugsTrailingEdge;
|
||||
if (!self->vertical)
|
||||
hugsSecondary = uiDarwinControlHugsBottom;
|
||||
for (bc in self->children) {
|
||||
if (!uiControlVisible(bc.c))
|
||||
continue;
|
||||
c = mkConstraint(self, self->secondaryStart,
|
||||
NSLayoutRelationEqual,
|
||||
[bc view], self->secondaryStart,
|
||||
1, 0,
|
||||
@"uiBox secondary start constraint");
|
||||
[self addConstraint:c];
|
||||
[self->otherConstraints addObject:c];
|
||||
c = mkConstraint([bc view], self->secondaryEnd,
|
||||
NSLayoutRelationLessThanOrEqual,
|
||||
self, self->secondaryEnd,
|
||||
1, 0,
|
||||
@"uiBox secondary end <= constraint");
|
||||
if ((*hugsSecondary)(uiDarwinControl(bc.c)))
|
||||
[c setPriority:NSLayoutPriorityDefaultLow];
|
||||
[self addConstraint:c];
|
||||
[self->otherConstraints addObject:c];
|
||||
c = mkConstraint([bc view], self->secondaryEnd,
|
||||
NSLayoutRelationEqual,
|
||||
self, self->secondaryEnd,
|
||||
1, 0,
|
||||
@"uiBox secondary end == constraint");
|
||||
if (!(*hugsSecondary)(uiDarwinControl(bc.c)))
|
||||
[c setPriority:NSLayoutPriorityDefaultLow];
|
||||
[self addConstraint:c];
|
||||
[self->otherConstraints addObject:c];
|
||||
}
|
||||
|
||||
// and make all stretchy controls the same size
|
||||
if ([self nStretchy] == 0)
|
||||
return;
|
||||
prev = nil; // first stretchy view
|
||||
for (bc in self->children) {
|
||||
if (!uiControlVisible(bc.c))
|
||||
continue;
|
||||
if (!bc.stretchy)
|
||||
continue;
|
||||
if (prev == nil) {
|
||||
prev = [bc view];
|
||||
continue;
|
||||
}
|
||||
c = mkConstraint(prev, self->primarySize,
|
||||
NSLayoutRelationEqual,
|
||||
[bc view], self->primarySize,
|
||||
1, 0,
|
||||
@"uiBox stretchy size constraint");
|
||||
[self addConstraint:c];
|
||||
[self->otherConstraints addObject:c];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)append:(uiControl *)c stretchy:(int)stretchy
|
||||
{
|
||||
boxChild *bc;
|
||||
NSLayoutPriority priority;
|
||||
int oldnStretchy;
|
||||
|
||||
bc = [boxChild new];
|
||||
bc.c = c;
|
||||
bc.stretchy = stretchy;
|
||||
bc.oldPrimaryHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(bc.c), self->primaryOrientation);
|
||||
bc.oldSecondaryHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(bc.c), self->secondaryOrientation);
|
||||
|
||||
uiControlSetParent(bc.c, uiControl(self->b));
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(bc.c), self);
|
||||
uiDarwinControlSyncEnableState(uiDarwinControl(bc.c), uiControlEnabledToUser(uiControl(self->b)));
|
||||
|
||||
// if a control is stretchy, it should not hug in the primary direction
|
||||
// otherwise, it should *forcibly* hug
|
||||
if (bc.stretchy)
|
||||
priority = NSLayoutPriorityDefaultLow;
|
||||
else
|
||||
// LONGTERM will default high work?
|
||||
priority = NSLayoutPriorityRequired;
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), priority, self->primaryOrientation);
|
||||
// make sure controls don't hug their secondary direction so they fill the width of the view
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), NSLayoutPriorityDefaultLow, self->secondaryOrientation);
|
||||
|
||||
oldnStretchy = [self nStretchy];
|
||||
[self->children addObject:bc];
|
||||
|
||||
[self establishOurConstraints];
|
||||
if (bc.stretchy)
|
||||
if (oldnStretchy == 0)
|
||||
uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->b));
|
||||
|
||||
[bc release]; // we don't need the initial reference now
|
||||
}
|
||||
|
||||
- (void)delete:(int)n
|
||||
{
|
||||
boxChild *bc;
|
||||
int stretchy;
|
||||
|
||||
bc = (boxChild *) [self->children objectAtIndex:n];
|
||||
stretchy = bc.stretchy;
|
||||
|
||||
uiControlSetParent(bc.c, NULL);
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(bc.c), nil);
|
||||
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), bc.oldPrimaryHuggingPri, self->primaryOrientation);
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), bc.oldSecondaryHuggingPri, self->secondaryOrientation);
|
||||
|
||||
[self->children removeObjectAtIndex:n];
|
||||
|
||||
[self establishOurConstraints];
|
||||
if (stretchy)
|
||||
if ([self nStretchy] == 0)
|
||||
uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->b));
|
||||
}
|
||||
|
||||
- (int)isPadded
|
||||
{
|
||||
return self->padded;
|
||||
}
|
||||
|
||||
- (void)setPadded:(int)p
|
||||
{
|
||||
CGFloat padding;
|
||||
NSLayoutConstraint *c;
|
||||
|
||||
self->padded = p;
|
||||
padding = [self paddingAmount];
|
||||
for (c in self->inBetweens)
|
||||
[c setConstant:-padding];
|
||||
}
|
||||
|
||||
- (BOOL)hugsTrailing
|
||||
{
|
||||
if (self->vertical) // always hug if vertical
|
||||
return YES;
|
||||
return [self nStretchy] != 0;
|
||||
}
|
||||
|
||||
- (BOOL)hugsBottom
|
||||
{
|
||||
if (!self->vertical) // always hug if horizontal
|
||||
return YES;
|
||||
return [self nStretchy] != 0;
|
||||
}
|
||||
|
||||
- (int)nStretchy
|
||||
{
|
||||
boxChild *bc;
|
||||
int n;
|
||||
|
||||
n = 0;
|
||||
for (bc in self->children) {
|
||||
if (!uiControlVisible(bc.c))
|
||||
continue;
|
||||
if (bc.stretchy)
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static void uiBoxDestroy(uiControl *c)
|
||||
{
|
||||
uiBox *b = uiBox(c);
|
||||
|
||||
[b->view onDestroy];
|
||||
[b->view release];
|
||||
uiFreeControl(uiControl(b));
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultHandle(uiBox, view)
|
||||
uiDarwinControlDefaultParent(uiBox, view)
|
||||
uiDarwinControlDefaultSetParent(uiBox, view)
|
||||
uiDarwinControlDefaultToplevel(uiBox, view)
|
||||
uiDarwinControlDefaultVisible(uiBox, view)
|
||||
uiDarwinControlDefaultShow(uiBox, view)
|
||||
uiDarwinControlDefaultHide(uiBox, view)
|
||||
uiDarwinControlDefaultEnabled(uiBox, view)
|
||||
uiDarwinControlDefaultEnable(uiBox, view)
|
||||
uiDarwinControlDefaultDisable(uiBox, view)
|
||||
|
||||
static void uiBoxSyncEnableState(uiDarwinControl *c, int enabled)
|
||||
{
|
||||
uiBox *b = uiBox(c);
|
||||
|
||||
if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(b), enabled))
|
||||
return;
|
||||
[b->view syncEnableStates:enabled];
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultSetSuperview(uiBox, view)
|
||||
|
||||
static BOOL uiBoxHugsTrailingEdge(uiDarwinControl *c)
|
||||
{
|
||||
uiBox *b = uiBox(c);
|
||||
|
||||
return [b->view hugsTrailing];
|
||||
}
|
||||
|
||||
static BOOL uiBoxHugsBottom(uiDarwinControl *c)
|
||||
{
|
||||
uiBox *b = uiBox(c);
|
||||
|
||||
return [b->view hugsBottom];
|
||||
}
|
||||
|
||||
static void uiBoxChildEdgeHuggingChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiBox *b = uiBox(c);
|
||||
|
||||
[b->view establishOurConstraints];
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultHuggingPriority(uiBox, view)
|
||||
uiDarwinControlDefaultSetHuggingPriority(uiBox, view)
|
||||
|
||||
static void uiBoxChildVisibilityChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiBox *b = uiBox(c);
|
||||
|
||||
[b->view establishOurConstraints];
|
||||
}
|
||||
|
||||
void uiBoxAppend(uiBox *b, uiControl *c, int stretchy)
|
||||
{
|
||||
// LONGTERM on other platforms
|
||||
// or at leat allow this and implicitly turn it into a spacer
|
||||
if (c == NULL)
|
||||
userbug("You cannot add NULL to a uiBox.");
|
||||
[b->view append:c stretchy:stretchy];
|
||||
}
|
||||
|
||||
void uiBoxDelete(uiBox *b, int n)
|
||||
{
|
||||
[b->view delete:n];
|
||||
}
|
||||
|
||||
int uiBoxPadded(uiBox *b)
|
||||
{
|
||||
return [b->view isPadded];
|
||||
}
|
||||
|
||||
void uiBoxSetPadded(uiBox *b, int padded)
|
||||
{
|
||||
[b->view setPadded:padded];
|
||||
}
|
||||
|
||||
static uiBox *finishNewBox(BOOL vertical)
|
||||
{
|
||||
uiBox *b;
|
||||
|
||||
uiDarwinNewControl(uiBox, b);
|
||||
|
||||
b->view = [[boxView alloc] initWithVertical:vertical b:b];
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
uiBox *uiNewHorizontalBox(void)
|
||||
{
|
||||
return finishNewBox(NO);
|
||||
}
|
||||
|
||||
uiBox *uiNewVerticalBox(void)
|
||||
{
|
||||
return finishNewBox(YES);
|
||||
}
|
||||
Vendored
+113
@@ -0,0 +1,113 @@
|
||||
// 13 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
struct uiButton {
|
||||
uiDarwinControl c;
|
||||
NSButton *button;
|
||||
void (*onClicked)(uiButton *, void *);
|
||||
void *onClickedData;
|
||||
};
|
||||
|
||||
@interface buttonDelegateClass : NSObject {
|
||||
struct mapTable *buttons;
|
||||
}
|
||||
- (IBAction)onClicked:(id)sender;
|
||||
- (void)registerButton:(uiButton *)b;
|
||||
- (void)unregisterButton:(uiButton *)b;
|
||||
@end
|
||||
|
||||
@implementation buttonDelegateClass
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
self->buttons = newMap();
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
mapDestroy(self->buttons);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (IBAction)onClicked:(id)sender
|
||||
{
|
||||
uiButton *b;
|
||||
|
||||
b = (uiButton *) mapGet(self->buttons, sender);
|
||||
(*(b->onClicked))(b, b->onClickedData);
|
||||
}
|
||||
|
||||
- (void)registerButton:(uiButton *)b
|
||||
{
|
||||
mapSet(self->buttons, b->button, b);
|
||||
[b->button setTarget:self];
|
||||
[b->button setAction:@selector(onClicked:)];
|
||||
}
|
||||
|
||||
- (void)unregisterButton:(uiButton *)b
|
||||
{
|
||||
[b->button setTarget:nil];
|
||||
mapDelete(self->buttons, b->button);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static buttonDelegateClass *buttonDelegate = nil;
|
||||
|
||||
uiDarwinControlAllDefaultsExceptDestroy(uiButton, button)
|
||||
|
||||
static void uiButtonDestroy(uiControl *c)
|
||||
{
|
||||
uiButton *b = uiButton(c);
|
||||
|
||||
[buttonDelegate unregisterButton:b];
|
||||
[b->button release];
|
||||
uiFreeControl(uiControl(b));
|
||||
}
|
||||
|
||||
char *uiButtonText(uiButton *b)
|
||||
{
|
||||
return uiDarwinNSStringToText([b->button title]);
|
||||
}
|
||||
|
||||
void uiButtonSetText(uiButton *b, const char *text)
|
||||
{
|
||||
[b->button setTitle:toNSString(text)];
|
||||
}
|
||||
|
||||
void uiButtonOnClicked(uiButton *b, void (*f)(uiButton *, void *), void *data)
|
||||
{
|
||||
b->onClicked = f;
|
||||
b->onClickedData = data;
|
||||
}
|
||||
|
||||
static void defaultOnClicked(uiButton *b, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
uiButton *uiNewButton(const char *text)
|
||||
{
|
||||
uiButton *b;
|
||||
|
||||
uiDarwinNewControl(uiButton, b);
|
||||
|
||||
b->button = [[NSButton alloc] initWithFrame:NSZeroRect];
|
||||
[b->button setTitle:toNSString(text)];
|
||||
[b->button setButtonType:NSMomentaryPushInButton];
|
||||
[b->button setBordered:YES];
|
||||
[b->button setBezelStyle:NSRoundedBezelStyle];
|
||||
uiDarwinSetControlFont(b->button, NSRegularControlSize);
|
||||
|
||||
if (buttonDelegate == nil) {
|
||||
buttonDelegate = [[buttonDelegateClass new] autorelease];
|
||||
[delegates addObject:buttonDelegate];
|
||||
}
|
||||
[buttonDelegate registerButton:b];
|
||||
uiButtonOnClicked(b, defaultOnClicked, NULL);
|
||||
|
||||
return b;
|
||||
}
|
||||
Vendored
+129
@@ -0,0 +1,129 @@
|
||||
// 14 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
struct uiCheckbox {
|
||||
uiDarwinControl c;
|
||||
NSButton *button;
|
||||
void (*onToggled)(uiCheckbox *, void *);
|
||||
void *onToggledData;
|
||||
};
|
||||
|
||||
@interface checkboxDelegateClass : NSObject {
|
||||
struct mapTable *buttons;
|
||||
}
|
||||
- (IBAction)onToggled:(id)sender;
|
||||
- (void)registerCheckbox:(uiCheckbox *)c;
|
||||
- (void)unregisterCheckbox:(uiCheckbox *)c;
|
||||
@end
|
||||
|
||||
@implementation checkboxDelegateClass
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
self->buttons = newMap();
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
mapDestroy(self->buttons);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (IBAction)onToggled:(id)sender
|
||||
{
|
||||
uiCheckbox *c;
|
||||
|
||||
c = (uiCheckbox *) mapGet(self->buttons, sender);
|
||||
(*(c->onToggled))(c, c->onToggledData);
|
||||
}
|
||||
|
||||
- (void)registerCheckbox:(uiCheckbox *)c
|
||||
{
|
||||
mapSet(self->buttons, c->button, c);
|
||||
[c->button setTarget:self];
|
||||
[c->button setAction:@selector(onToggled:)];
|
||||
}
|
||||
|
||||
- (void)unregisterCheckbox:(uiCheckbox *)c
|
||||
{
|
||||
[c->button setTarget:nil];
|
||||
mapDelete(self->buttons, c->button);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static checkboxDelegateClass *checkboxDelegate = nil;
|
||||
|
||||
uiDarwinControlAllDefaultsExceptDestroy(uiCheckbox, button)
|
||||
|
||||
static void uiCheckboxDestroy(uiControl *cc)
|
||||
{
|
||||
uiCheckbox *c = uiCheckbox(cc);
|
||||
|
||||
[checkboxDelegate unregisterCheckbox:c];
|
||||
[c->button release];
|
||||
uiFreeControl(uiControl(c));
|
||||
}
|
||||
|
||||
char *uiCheckboxText(uiCheckbox *c)
|
||||
{
|
||||
return uiDarwinNSStringToText([c->button title]);
|
||||
}
|
||||
|
||||
void uiCheckboxSetText(uiCheckbox *c, const char *text)
|
||||
{
|
||||
[c->button setTitle:toNSString(text)];
|
||||
}
|
||||
|
||||
void uiCheckboxOnToggled(uiCheckbox *c, void (*f)(uiCheckbox *, void *), void *data)
|
||||
{
|
||||
c->onToggled = f;
|
||||
c->onToggledData = data;
|
||||
}
|
||||
|
||||
int uiCheckboxChecked(uiCheckbox *c)
|
||||
{
|
||||
return [c->button state] == NSOnState;
|
||||
}
|
||||
|
||||
void uiCheckboxSetChecked(uiCheckbox *c, int checked)
|
||||
{
|
||||
NSInteger state;
|
||||
|
||||
state = NSOnState;
|
||||
if (!checked)
|
||||
state = NSOffState;
|
||||
[c->button setState:state];
|
||||
}
|
||||
|
||||
static void defaultOnToggled(uiCheckbox *c, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
uiCheckbox *uiNewCheckbox(const char *text)
|
||||
{
|
||||
uiCheckbox *c;
|
||||
|
||||
uiDarwinNewControl(uiCheckbox, c);
|
||||
|
||||
c->button = [[NSButton alloc] initWithFrame:NSZeroRect];
|
||||
[c->button setTitle:toNSString(text)];
|
||||
[c->button setButtonType:NSSwitchButton];
|
||||
// doesn't seem to have an associated bezel style
|
||||
[c->button setBordered:NO];
|
||||
[c->button setTransparent:NO];
|
||||
uiDarwinSetControlFont(c->button, NSRegularControlSize);
|
||||
|
||||
if (checkboxDelegate == nil) {
|
||||
checkboxDelegate = [[checkboxDelegateClass new] autorelease];
|
||||
[delegates addObject:checkboxDelegate];
|
||||
}
|
||||
[checkboxDelegate registerCheckbox:c];
|
||||
uiCheckboxOnToggled(c, defaultOnToggled, NULL);
|
||||
|
||||
return c;
|
||||
}
|
||||
Vendored
+159
@@ -0,0 +1,159 @@
|
||||
// 15 may 2016
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// TODO no intrinsic height?
|
||||
|
||||
@interface colorButton : NSColorWell {
|
||||
uiColorButton *libui_b;
|
||||
BOOL libui_changing;
|
||||
BOOL libui_setting;
|
||||
}
|
||||
- (id)initWithFrame:(NSRect)frame libuiColorButton:(uiColorButton *)b;
|
||||
- (void)deactivateOnClose:(NSNotification *)note;
|
||||
- (void)libuiColor:(double *)r g:(double *)g b:(double *)b a:(double *)a;
|
||||
- (void)libuiSetColor:(double)r g:(double)g b:(double)b a:(double)a;
|
||||
@end
|
||||
|
||||
// only one may be active at one time
|
||||
static colorButton *activeColorButton = nil;
|
||||
|
||||
struct uiColorButton {
|
||||
uiDarwinControl c;
|
||||
colorButton *button;
|
||||
void (*onChanged)(uiColorButton *, void *);
|
||||
void *onChangedData;
|
||||
};
|
||||
|
||||
@implementation colorButton
|
||||
|
||||
- (id)initWithFrame:(NSRect)frame libuiColorButton:(uiColorButton *)b
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
// the default color is white; set it to black first (see -setColor: below for why we do it first)
|
||||
[self libuiSetColor:0.0 g:0.0 b:0.0 a:1.0];
|
||||
|
||||
self->libui_b = b;
|
||||
self->libui_changing = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)activate:(BOOL)exclusive
|
||||
{
|
||||
if (activeColorButton != nil)
|
||||
activeColorButton->libui_changing = YES;
|
||||
[NSColorPanel setPickerMask:NSColorPanelAllModesMask];
|
||||
[[NSColorPanel sharedColorPanel] setShowsAlpha:YES];
|
||||
[super activate:YES];
|
||||
activeColorButton = self;
|
||||
// see stddialogs.m for details
|
||||
[[NSColorPanel sharedColorPanel] setWorksWhenModal:NO];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(deactivateOnClose:)
|
||||
name:NSWindowWillCloseNotification
|
||||
object:[NSColorPanel sharedColorPanel]];
|
||||
}
|
||||
|
||||
- (void)deactivate
|
||||
{
|
||||
[super deactivate];
|
||||
activeColorButton = nil;
|
||||
if (!self->libui_changing)
|
||||
[[NSColorPanel sharedColorPanel] orderOut:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:NSWindowWillCloseNotification
|
||||
object:[NSColorPanel sharedColorPanel]];
|
||||
self->libui_changing = NO;
|
||||
}
|
||||
|
||||
- (void)deactivateOnClose:(NSNotification *)note
|
||||
{
|
||||
[self deactivate];
|
||||
}
|
||||
|
||||
- (void)setColor:(NSColor *)color
|
||||
{
|
||||
uiColorButton *b = self->libui_b;
|
||||
|
||||
[super setColor:color];
|
||||
// this is called by NSColorWell's init, so we have to guard
|
||||
// also don't signal during a programmatic change
|
||||
if (b != nil && !self->libui_setting)
|
||||
(*(b->onChanged))(b, b->onChangedData);
|
||||
}
|
||||
|
||||
- (void)libuiColor:(double *)r g:(double *)g b:(double *)b a:(double *)a
|
||||
{
|
||||
NSColor *rgba;
|
||||
CGFloat cr, cg, cb, ca;
|
||||
|
||||
// the given color may not be an RGBA color, which will cause the -getRed:green:blue:alpha: call to throw an exception
|
||||
rgba = [[self color] colorUsingColorSpace:[NSColorSpace sRGBColorSpace]];
|
||||
[rgba getRed:&cr green:&cg blue:&cb alpha:&ca];
|
||||
*r = cr;
|
||||
*g = cg;
|
||||
*b = cb;
|
||||
*a = ca;
|
||||
// rgba will be autoreleased since it isn't a new or init call
|
||||
}
|
||||
|
||||
- (void)libuiSetColor:(double)r g:(double)g b:(double)b a:(double)a
|
||||
{
|
||||
self->libui_setting = YES;
|
||||
[self setColor:[NSColor colorWithSRGBRed:r green:g blue:b alpha:a]];
|
||||
self->libui_setting = NO;
|
||||
}
|
||||
|
||||
// NSColorWell has no intrinsic size by default; give it the default Interface Builder size.
|
||||
- (NSSize)intrinsicContentSize
|
||||
{
|
||||
return NSMakeSize(44, 23);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
uiDarwinControlAllDefaults(uiColorButton, button)
|
||||
|
||||
// we do not want color change events to be sent to any controls other than the color buttons
|
||||
// see main.m for more details
|
||||
BOOL colorButtonInhibitSendAction(SEL sel, id from, id to)
|
||||
{
|
||||
if (sel != @selector(changeColor:))
|
||||
return NO;
|
||||
return ![to isKindOfClass:[colorButton class]];
|
||||
}
|
||||
|
||||
static void defaultOnChanged(uiColorButton *b, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
void uiColorButtonColor(uiColorButton *b, double *r, double *g, double *bl, double *a)
|
||||
{
|
||||
[b->button libuiColor:r g:g b:bl a:a];
|
||||
}
|
||||
|
||||
void uiColorButtonSetColor(uiColorButton *b, double r, double g, double bl, double a)
|
||||
{
|
||||
[b->button libuiSetColor:r g:g b:bl a:a];
|
||||
}
|
||||
|
||||
void uiColorButtonOnChanged(uiColorButton *b, void (*f)(uiColorButton *, void *), void *data)
|
||||
{
|
||||
b->onChanged = f;
|
||||
b->onChangedData = data;
|
||||
}
|
||||
|
||||
uiColorButton *uiNewColorButton(void)
|
||||
{
|
||||
uiColorButton *b;
|
||||
|
||||
uiDarwinNewControl(uiColorButton, b);
|
||||
|
||||
b->button = [[colorButton alloc] initWithFrame:NSZeroRect libuiColorButton:b];
|
||||
|
||||
uiColorButtonOnChanged(b, defaultOnChanged, NULL);
|
||||
|
||||
return b;
|
||||
}
|
||||
Vendored
+145
@@ -0,0 +1,145 @@
|
||||
// 14 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// NSComboBoxes have no intrinsic width; we'll use the default Interface Builder width for them.
|
||||
// NSPopUpButton is fine.
|
||||
#define comboboxWidth 96
|
||||
|
||||
struct uiCombobox {
|
||||
uiDarwinControl c;
|
||||
NSPopUpButton *pb;
|
||||
NSArrayController *pbac;
|
||||
void (*onSelected)(uiCombobox *, void *);
|
||||
void *onSelectedData;
|
||||
};
|
||||
|
||||
@interface comboboxDelegateClass : NSObject {
|
||||
struct mapTable *comboboxes;
|
||||
}
|
||||
- (IBAction)onSelected:(id)sender;
|
||||
- (void)registerCombobox:(uiCombobox *)c;
|
||||
- (void)unregisterCombobox:(uiCombobox *)c;
|
||||
@end
|
||||
|
||||
@implementation comboboxDelegateClass
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
self->comboboxes = newMap();
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
mapDestroy(self->comboboxes);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (IBAction)onSelected:(id)sender
|
||||
{
|
||||
uiCombobox *c;
|
||||
|
||||
c = uiCombobox(mapGet(self->comboboxes, sender));
|
||||
(*(c->onSelected))(c, c->onSelectedData);
|
||||
}
|
||||
|
||||
- (void)registerCombobox:(uiCombobox *)c
|
||||
{
|
||||
mapSet(self->comboboxes, c->pb, c);
|
||||
[c->pb setTarget:self];
|
||||
[c->pb setAction:@selector(onSelected:)];
|
||||
}
|
||||
|
||||
- (void)unregisterCombobox:(uiCombobox *)c
|
||||
{
|
||||
[c->pb setTarget:nil];
|
||||
mapDelete(self->comboboxes, c->pb);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static comboboxDelegateClass *comboboxDelegate = nil;
|
||||
|
||||
uiDarwinControlAllDefaultsExceptDestroy(uiCombobox, pb)
|
||||
|
||||
static void uiComboboxDestroy(uiControl *cc)
|
||||
{
|
||||
uiCombobox *c = uiCombobox(cc);
|
||||
|
||||
[comboboxDelegate unregisterCombobox:c];
|
||||
[c->pb unbind:@"contentObjects"];
|
||||
[c->pb unbind:@"selectedIndex"];
|
||||
[c->pbac release];
|
||||
[c->pb release];
|
||||
uiFreeControl(uiControl(c));
|
||||
}
|
||||
|
||||
void uiComboboxAppend(uiCombobox *c, const char *text)
|
||||
{
|
||||
[c->pbac addObject:toNSString(text)];
|
||||
}
|
||||
|
||||
int uiComboboxSelected(uiCombobox *c)
|
||||
{
|
||||
return [c->pb indexOfSelectedItem];
|
||||
}
|
||||
|
||||
void uiComboboxSetSelected(uiCombobox *c, int n)
|
||||
{
|
||||
[c->pb selectItemAtIndex:n];
|
||||
}
|
||||
|
||||
void uiComboboxOnSelected(uiCombobox *c, void (*f)(uiCombobox *c, void *data), void *data)
|
||||
{
|
||||
c->onSelected = f;
|
||||
c->onSelectedData = data;
|
||||
}
|
||||
|
||||
static void defaultOnSelected(uiCombobox *c, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
uiCombobox *uiNewCombobox(void)
|
||||
{
|
||||
uiCombobox *c;
|
||||
NSPopUpButtonCell *pbcell;
|
||||
|
||||
uiDarwinNewControl(uiCombobox, c);
|
||||
|
||||
c->pb = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO];
|
||||
[c->pb setPreferredEdge:NSMinYEdge];
|
||||
pbcell = (NSPopUpButtonCell *) [c->pb cell];
|
||||
[pbcell setArrowPosition:NSPopUpArrowAtBottom];
|
||||
// the font defined by Interface Builder is Menu 13, which is lol
|
||||
// just use the regular control size for consistency
|
||||
uiDarwinSetControlFont(c->pb, NSRegularControlSize);
|
||||
|
||||
// NSPopUpButton doesn't work like a combobox
|
||||
// - it automatically selects the first item
|
||||
// - it doesn't support duplicates
|
||||
// but we can use a NSArrayController and Cocoa bindings to bypass these restrictions
|
||||
c->pbac = [NSArrayController new];
|
||||
[c->pbac setAvoidsEmptySelection:NO];
|
||||
[c->pbac setSelectsInsertedObjects:NO];
|
||||
[c->pbac setAutomaticallyRearrangesObjects:NO];
|
||||
[c->pb bind:@"contentValues"
|
||||
toObject:c->pbac
|
||||
withKeyPath:@"arrangedObjects"
|
||||
options:nil];
|
||||
[c->pb bind:@"selectedIndex"
|
||||
toObject:c->pbac
|
||||
withKeyPath:@"selectionIndex"
|
||||
options:nil];
|
||||
|
||||
if (comboboxDelegate == nil) {
|
||||
comboboxDelegate = [[comboboxDelegateClass new] autorelease];
|
||||
[delegates addObject:comboboxDelegate];
|
||||
}
|
||||
[comboboxDelegate registerCombobox:c];
|
||||
uiComboboxOnSelected(c, defaultOnSelected, NULL);
|
||||
|
||||
return c;
|
||||
}
|
||||
Vendored
+84
@@ -0,0 +1,84 @@
|
||||
// 16 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
void uiDarwinControlSyncEnableState(uiDarwinControl *c, int state)
|
||||
{
|
||||
(*(c->SyncEnableState))(c, state);
|
||||
}
|
||||
|
||||
void uiDarwinControlSetSuperview(uiDarwinControl *c, NSView *superview)
|
||||
{
|
||||
(*(c->SetSuperview))(c, superview);
|
||||
}
|
||||
|
||||
BOOL uiDarwinControlHugsTrailingEdge(uiDarwinControl *c)
|
||||
{
|
||||
return (*(c->HugsTrailingEdge))(c);
|
||||
}
|
||||
|
||||
BOOL uiDarwinControlHugsBottom(uiDarwinControl *c)
|
||||
{
|
||||
return (*(c->HugsBottom))(c);
|
||||
}
|
||||
|
||||
void uiDarwinControlChildEdgeHuggingChanged(uiDarwinControl *c)
|
||||
{
|
||||
(*(c->ChildEdgeHuggingChanged))(c);
|
||||
}
|
||||
|
||||
NSLayoutPriority uiDarwinControlHuggingPriority(uiDarwinControl *c, NSLayoutConstraintOrientation orientation)
|
||||
{
|
||||
return (*(c->HuggingPriority))(c, orientation);
|
||||
}
|
||||
|
||||
void uiDarwinControlSetHuggingPriority(uiDarwinControl *c, NSLayoutPriority priority, NSLayoutConstraintOrientation orientation)
|
||||
{
|
||||
(*(c->SetHuggingPriority))(c, priority, orientation);
|
||||
}
|
||||
|
||||
void uiDarwinControlChildVisibilityChanged(uiDarwinControl *c)
|
||||
{
|
||||
(*(c->ChildVisibilityChanged))(c);
|
||||
}
|
||||
|
||||
void uiDarwinSetControlFont(NSControl *c, NSControlSize size)
|
||||
{
|
||||
[c setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:size]]];
|
||||
}
|
||||
|
||||
#define uiDarwinControlSignature 0x44617277
|
||||
|
||||
uiDarwinControl *uiDarwinAllocControl(size_t n, uint32_t typesig, const char *typenamestr)
|
||||
{
|
||||
return uiDarwinControl(uiAllocControl(n, uiDarwinControlSignature, typesig, typenamestr));
|
||||
}
|
||||
|
||||
BOOL uiDarwinShouldStopSyncEnableState(uiDarwinControl *c, BOOL enabled)
|
||||
{
|
||||
int ce;
|
||||
|
||||
ce = uiControlEnabled(uiControl(c));
|
||||
// only stop if we're going from disabled back to enabled; don't stop under any other condition
|
||||
// (if we stop when going from enabled to disabled then enabled children of a disabled control won't get disabled at the OS level)
|
||||
if (!ce && enabled)
|
||||
return YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
void uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiControl *parent;
|
||||
|
||||
parent = uiControlParent(uiControl(c));
|
||||
if (parent != NULL)
|
||||
uiDarwinControlChildEdgeHuggingChanged(uiDarwinControl(parent));
|
||||
}
|
||||
|
||||
void uiDarwinNotifyVisibilityChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiControl *parent;
|
||||
|
||||
parent = uiControlParent(uiControl(c));
|
||||
if (parent != NULL)
|
||||
uiDarwinControlChildVisibilityChanged(uiDarwinControl(parent));
|
||||
}
|
||||
Vendored
+42
@@ -0,0 +1,42 @@
|
||||
// 14 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
struct uiDateTimePicker {
|
||||
uiDarwinControl c;
|
||||
NSDatePicker *dp;
|
||||
};
|
||||
|
||||
uiDarwinControlAllDefaults(uiDateTimePicker, dp)
|
||||
|
||||
static uiDateTimePicker *finishNewDateTimePicker(NSDatePickerElementFlags elements)
|
||||
{
|
||||
uiDateTimePicker *d;
|
||||
|
||||
uiDarwinNewControl(uiDateTimePicker, d);
|
||||
|
||||
d->dp = [[NSDatePicker alloc] initWithFrame:NSZeroRect];
|
||||
[d->dp setBordered:NO];
|
||||
[d->dp setBezeled:YES];
|
||||
[d->dp setDrawsBackground:YES];
|
||||
[d->dp setDatePickerStyle:NSTextFieldAndStepperDatePickerStyle];
|
||||
[d->dp setDatePickerElements:elements];
|
||||
[d->dp setDatePickerMode:NSSingleDateMode];
|
||||
uiDarwinSetControlFont(d->dp, NSRegularControlSize);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
uiDateTimePicker *uiNewDateTimePicker(void)
|
||||
{
|
||||
return finishNewDateTimePicker(NSYearMonthDayDatePickerElementFlag | NSHourMinuteSecondDatePickerElementFlag);
|
||||
}
|
||||
|
||||
uiDateTimePicker *uiNewDatePicker(void)
|
||||
{
|
||||
return finishNewDateTimePicker(NSYearMonthDayDatePickerElementFlag);
|
||||
}
|
||||
|
||||
uiDateTimePicker *uiNewTimePicker(void)
|
||||
{
|
||||
return finishNewDateTimePicker(NSHourMinuteSecondDatePickerElementFlag);
|
||||
}
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
// 13 may 2016
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// LONGTERM don't halt on release builds
|
||||
|
||||
void realbug(const char *file, const char *line, const char *func, const char *prefix, const char *format, va_list ap)
|
||||
{
|
||||
NSMutableString *str;
|
||||
NSString *formatted;
|
||||
|
||||
str = [NSMutableString new];
|
||||
[str appendString:[NSString stringWithFormat:@"[libui] %s:%s:%s() %s", file, line, func, prefix]];
|
||||
formatted = [[NSString alloc] initWithFormat:[NSString stringWithUTF8String:format] arguments:ap];
|
||||
[str appendString:formatted];
|
||||
[formatted release];
|
||||
NSLog(@"%@", str);
|
||||
[str release];
|
||||
__builtin_trap();
|
||||
}
|
||||
Vendored
+454
@@ -0,0 +1,454 @@
|
||||
// 6 september 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
struct uiDrawPath {
|
||||
CGMutablePathRef path;
|
||||
uiDrawFillMode fillMode;
|
||||
BOOL ended;
|
||||
};
|
||||
|
||||
uiDrawPath *uiDrawNewPath(uiDrawFillMode mode)
|
||||
{
|
||||
uiDrawPath *p;
|
||||
|
||||
p = uiNew(uiDrawPath);
|
||||
p->path = CGPathCreateMutable();
|
||||
p->fillMode = mode;
|
||||
return p;
|
||||
}
|
||||
|
||||
void uiDrawFreePath(uiDrawPath *p)
|
||||
{
|
||||
CGPathRelease((CGPathRef) (p->path));
|
||||
uiFree(p);
|
||||
}
|
||||
|
||||
void uiDrawPathNewFigure(uiDrawPath *p, double x, double y)
|
||||
{
|
||||
if (p->ended)
|
||||
userbug("You cannot call uiDrawPathNewFigure() on a uiDrawPath that has already been ended. (path; %p)", p);
|
||||
CGPathMoveToPoint(p->path, NULL, x, y);
|
||||
}
|
||||
|
||||
void uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)
|
||||
{
|
||||
double sinStart, cosStart;
|
||||
double startx, starty;
|
||||
|
||||
if (p->ended)
|
||||
userbug("You cannot call uiDrawPathNewFigureWithArc() on a uiDrawPath that has already been ended. (path; %p)", p);
|
||||
sinStart = sin(startAngle);
|
||||
cosStart = cos(startAngle);
|
||||
startx = xCenter + radius * cosStart;
|
||||
starty = yCenter + radius * sinStart;
|
||||
CGPathMoveToPoint(p->path, NULL, startx, starty);
|
||||
uiDrawPathArcTo(p, xCenter, yCenter, radius, startAngle, sweep, negative);
|
||||
}
|
||||
|
||||
void uiDrawPathLineTo(uiDrawPath *p, double x, double y)
|
||||
{
|
||||
// TODO refine this to require being in a path
|
||||
if (p->ended)
|
||||
implbug("attempt to add line to ended path in uiDrawPathLineTo()");
|
||||
CGPathAddLineToPoint(p->path, NULL, x, y);
|
||||
}
|
||||
|
||||
void uiDrawPathArcTo(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)
|
||||
{
|
||||
bool cw;
|
||||
|
||||
// TODO likewise
|
||||
if (p->ended)
|
||||
implbug("attempt to add arc to ended path in uiDrawPathArcTo()");
|
||||
if (sweep > 2 * uiPi)
|
||||
sweep = 2 * uiPi;
|
||||
cw = false;
|
||||
if (negative)
|
||||
cw = true;
|
||||
CGPathAddArc(p->path, NULL,
|
||||
xCenter, yCenter,
|
||||
radius,
|
||||
startAngle, startAngle + sweep,
|
||||
cw);
|
||||
}
|
||||
|
||||
void uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, double c2y, double endX, double endY)
|
||||
{
|
||||
// TODO likewise
|
||||
if (p->ended)
|
||||
implbug("attempt to add bezier to ended path in uiDrawPathBezierTo()");
|
||||
CGPathAddCurveToPoint(p->path, NULL,
|
||||
c1x, c1y,
|
||||
c2x, c2y,
|
||||
endX, endY);
|
||||
}
|
||||
|
||||
void uiDrawPathCloseFigure(uiDrawPath *p)
|
||||
{
|
||||
// TODO likewise
|
||||
if (p->ended)
|
||||
implbug("attempt to close figure of ended path in uiDrawPathCloseFigure()");
|
||||
CGPathCloseSubpath(p->path);
|
||||
}
|
||||
|
||||
void uiDrawPathAddRectangle(uiDrawPath *p, double x, double y, double width, double height)
|
||||
{
|
||||
if (p->ended)
|
||||
userbug("You cannot call uiDrawPathAddRectangle() on a uiDrawPath that has already been ended. (path; %p)", p);
|
||||
CGPathAddRect(p->path, NULL, CGRectMake(x, y, width, height));
|
||||
}
|
||||
|
||||
void uiDrawPathEnd(uiDrawPath *p)
|
||||
{
|
||||
p->ended = TRUE;
|
||||
}
|
||||
|
||||
struct uiDrawContext {
|
||||
CGContextRef c;
|
||||
CGFloat height; // needed for text; see below
|
||||
};
|
||||
|
||||
uiDrawContext *newContext(CGContextRef ctxt, CGFloat height)
|
||||
{
|
||||
uiDrawContext *c;
|
||||
|
||||
c = uiNew(uiDrawContext);
|
||||
c->c = ctxt;
|
||||
c->height = height;
|
||||
return c;
|
||||
}
|
||||
|
||||
void freeContext(uiDrawContext *c)
|
||||
{
|
||||
uiFree(c);
|
||||
}
|
||||
|
||||
// a stroke is identical to a fill of a stroked path
|
||||
// we need to do this in order to stroke with a gradient; see http://stackoverflow.com/a/25034854/3408572
|
||||
// doing this for other brushes works too
|
||||
void uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStrokeParams *p)
|
||||
{
|
||||
CGLineCap cap;
|
||||
CGLineJoin join;
|
||||
CGPathRef dashPath;
|
||||
CGFloat *dashes;
|
||||
size_t i;
|
||||
uiDrawPath p2;
|
||||
|
||||
if (!path->ended)
|
||||
userbug("You cannot call uiDrawStroke() on a uiDrawPath that has not been ended. (path: %p)", path);
|
||||
|
||||
switch (p->Cap) {
|
||||
case uiDrawLineCapFlat:
|
||||
cap = kCGLineCapButt;
|
||||
break;
|
||||
case uiDrawLineCapRound:
|
||||
cap = kCGLineCapRound;
|
||||
break;
|
||||
case uiDrawLineCapSquare:
|
||||
cap = kCGLineCapSquare;
|
||||
break;
|
||||
}
|
||||
switch (p->Join) {
|
||||
case uiDrawLineJoinMiter:
|
||||
join = kCGLineJoinMiter;
|
||||
break;
|
||||
case uiDrawLineJoinRound:
|
||||
join = kCGLineJoinRound;
|
||||
break;
|
||||
case uiDrawLineJoinBevel:
|
||||
join = kCGLineJoinBevel;
|
||||
break;
|
||||
}
|
||||
|
||||
// create a temporary path identical to the previous one
|
||||
dashPath = (CGPathRef) path->path;
|
||||
if (p->NumDashes != 0) {
|
||||
dashes = (CGFloat *) uiAlloc(p->NumDashes * sizeof (CGFloat), "CGFloat[]");
|
||||
for (i = 0; i < p->NumDashes; i++)
|
||||
dashes[i] = p->Dashes[i];
|
||||
dashPath = CGPathCreateCopyByDashingPath(path->path,
|
||||
NULL,
|
||||
p->DashPhase,
|
||||
dashes,
|
||||
p->NumDashes);
|
||||
uiFree(dashes);
|
||||
}
|
||||
// the documentation is wrong: this produces a path suitable for calling CGPathCreateCopyByStrokingPath(), not for filling directly
|
||||
// the cast is safe; we never modify the CGPathRef and always cast it back to a CGPathRef anyway
|
||||
p2.path = (CGMutablePathRef) CGPathCreateCopyByStrokingPath(dashPath,
|
||||
NULL,
|
||||
p->Thickness,
|
||||
cap,
|
||||
join,
|
||||
p->MiterLimit);
|
||||
if (p->NumDashes != 0)
|
||||
CGPathRelease(dashPath);
|
||||
|
||||
// always draw stroke fills using the winding rule
|
||||
// otherwise intersecting figures won't draw correctly
|
||||
p2.fillMode = uiDrawFillModeWinding;
|
||||
p2.ended = path->ended;
|
||||
uiDrawFill(c, &p2, b);
|
||||
// and clean up
|
||||
CGPathRelease((CGPathRef) (p2.path));
|
||||
}
|
||||
|
||||
// for a solid fill, we can merely have Core Graphics fill directly
|
||||
static void fillSolid(CGContextRef ctxt, uiDrawPath *p, uiDrawBrush *b)
|
||||
{
|
||||
// TODO this uses DeviceRGB; switch to sRGB
|
||||
CGContextSetRGBFillColor(ctxt, b->R, b->G, b->B, b->A);
|
||||
switch (p->fillMode) {
|
||||
case uiDrawFillModeWinding:
|
||||
CGContextFillPath(ctxt);
|
||||
break;
|
||||
case uiDrawFillModeAlternate:
|
||||
CGContextEOFillPath(ctxt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// for a gradient fill, we need to clip to the path and then draw the gradient
|
||||
// see http://stackoverflow.com/a/25034854/3408572
|
||||
static void fillGradient(CGContextRef ctxt, uiDrawPath *p, uiDrawBrush *b)
|
||||
{
|
||||
CGGradientRef gradient;
|
||||
CGColorSpaceRef colorspace;
|
||||
CGFloat *colors;
|
||||
CGFloat *locations;
|
||||
size_t i;
|
||||
|
||||
// gradients need a color space
|
||||
// for consistency with windows, use sRGB
|
||||
colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
|
||||
|
||||
// make the gradient
|
||||
colors = uiAlloc(b->NumStops * 4 * sizeof (CGFloat), "CGFloat[]");
|
||||
locations = uiAlloc(b->NumStops * sizeof (CGFloat), "CGFloat[]");
|
||||
for (i = 0; i < b->NumStops; i++) {
|
||||
colors[i * 4 + 0] = b->Stops[i].R;
|
||||
colors[i * 4 + 1] = b->Stops[i].G;
|
||||
colors[i * 4 + 2] = b->Stops[i].B;
|
||||
colors[i * 4 + 3] = b->Stops[i].A;
|
||||
locations[i] = b->Stops[i].Pos;
|
||||
}
|
||||
gradient = CGGradientCreateWithColorComponents(colorspace, colors, locations, b->NumStops);
|
||||
uiFree(locations);
|
||||
uiFree(colors);
|
||||
|
||||
// because we're mucking with clipping, we need to save the graphics state and restore it later
|
||||
CGContextSaveGState(ctxt);
|
||||
|
||||
// clip
|
||||
switch (p->fillMode) {
|
||||
case uiDrawFillModeWinding:
|
||||
CGContextClip(ctxt);
|
||||
break;
|
||||
case uiDrawFillModeAlternate:
|
||||
CGContextEOClip(ctxt);
|
||||
break;
|
||||
}
|
||||
|
||||
// draw the gradient
|
||||
switch (b->Type) {
|
||||
case uiDrawBrushTypeLinearGradient:
|
||||
CGContextDrawLinearGradient(ctxt,
|
||||
gradient,
|
||||
CGPointMake(b->X0, b->Y0),
|
||||
CGPointMake(b->X1, b->Y1),
|
||||
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
|
||||
break;
|
||||
case uiDrawBrushTypeRadialGradient:
|
||||
CGContextDrawRadialGradient(ctxt,
|
||||
gradient,
|
||||
CGPointMake(b->X0, b->Y0),
|
||||
// make the start circle radius 0 to make it a point
|
||||
0,
|
||||
CGPointMake(b->X1, b->Y1),
|
||||
b->OuterRadius,
|
||||
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
|
||||
break;
|
||||
}
|
||||
|
||||
// and clean up
|
||||
CGContextRestoreGState(ctxt);
|
||||
CGGradientRelease(gradient);
|
||||
CGColorSpaceRelease(colorspace);
|
||||
}
|
||||
|
||||
void uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b)
|
||||
{
|
||||
if (!path->ended)
|
||||
userbug("You cannot call uiDrawStroke() on a uiDrawPath that has not been ended. (path: %p)", path);
|
||||
CGContextAddPath(c->c, (CGPathRef) (path->path));
|
||||
switch (b->Type) {
|
||||
case uiDrawBrushTypeSolid:
|
||||
fillSolid(c->c, path, b);
|
||||
return;
|
||||
case uiDrawBrushTypeLinearGradient:
|
||||
case uiDrawBrushTypeRadialGradient:
|
||||
fillGradient(c->c, path, b);
|
||||
return;
|
||||
// case uiDrawBrushTypeImage:
|
||||
// TODO
|
||||
return;
|
||||
}
|
||||
userbug("Unknown brush type %d passed to uiDrawFill().", b->Type);
|
||||
}
|
||||
|
||||
static void m2c(uiDrawMatrix *m, CGAffineTransform *c)
|
||||
{
|
||||
c->a = m->M11;
|
||||
c->b = m->M12;
|
||||
c->c = m->M21;
|
||||
c->d = m->M22;
|
||||
c->tx = m->M31;
|
||||
c->ty = m->M32;
|
||||
}
|
||||
|
||||
static void c2m(CGAffineTransform *c, uiDrawMatrix *m)
|
||||
{
|
||||
m->M11 = c->a;
|
||||
m->M12 = c->b;
|
||||
m->M21 = c->c;
|
||||
m->M22 = c->d;
|
||||
m->M31 = c->tx;
|
||||
m->M32 = c->ty;
|
||||
}
|
||||
|
||||
void uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y)
|
||||
{
|
||||
CGAffineTransform c;
|
||||
|
||||
m2c(m, &c);
|
||||
c = CGAffineTransformTranslate(c, x, y);
|
||||
c2m(&c, m);
|
||||
}
|
||||
|
||||
void uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x, double y)
|
||||
{
|
||||
CGAffineTransform c;
|
||||
double xt, yt;
|
||||
|
||||
m2c(m, &c);
|
||||
xt = x;
|
||||
yt = y;
|
||||
scaleCenter(xCenter, yCenter, &xt, &yt);
|
||||
c = CGAffineTransformTranslate(c, xt, yt);
|
||||
c = CGAffineTransformScale(c, x, y);
|
||||
c = CGAffineTransformTranslate(c, -xt, -yt);
|
||||
c2m(&c, m);
|
||||
}
|
||||
|
||||
void uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount)
|
||||
{
|
||||
CGAffineTransform c;
|
||||
|
||||
m2c(m, &c);
|
||||
c = CGAffineTransformTranslate(c, x, y);
|
||||
c = CGAffineTransformRotate(c, amount);
|
||||
c = CGAffineTransformTranslate(c, -x, -y);
|
||||
c2m(&c, m);
|
||||
}
|
||||
|
||||
void uiDrawMatrixSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount)
|
||||
{
|
||||
fallbackSkew(m, x, y, xamount, yamount);
|
||||
}
|
||||
|
||||
void uiDrawMatrixMultiply(uiDrawMatrix *dest, uiDrawMatrix *src)
|
||||
{
|
||||
CGAffineTransform c;
|
||||
CGAffineTransform d;
|
||||
|
||||
m2c(dest, &c);
|
||||
m2c(src, &d);
|
||||
c = CGAffineTransformConcat(c, d);
|
||||
c2m(&c, dest);
|
||||
}
|
||||
|
||||
// there is no test for invertibility; CGAffineTransformInvert() is merely documented as returning the matrix unchanged if it isn't invertible
|
||||
// therefore, special care must be taken to catch matrices who are their own inverses
|
||||
// TODO figure out which matrices these are and do so
|
||||
int uiDrawMatrixInvertible(uiDrawMatrix *m)
|
||||
{
|
||||
CGAffineTransform c, d;
|
||||
|
||||
m2c(m, &c);
|
||||
d = CGAffineTransformInvert(c);
|
||||
return CGAffineTransformEqualToTransform(c, d) == false;
|
||||
}
|
||||
|
||||
int uiDrawMatrixInvert(uiDrawMatrix *m)
|
||||
{
|
||||
CGAffineTransform c, d;
|
||||
|
||||
m2c(m, &c);
|
||||
d = CGAffineTransformInvert(c);
|
||||
if (CGAffineTransformEqualToTransform(c, d))
|
||||
return 0;
|
||||
c2m(&d, m);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void uiDrawMatrixTransformPoint(uiDrawMatrix *m, double *x, double *y)
|
||||
{
|
||||
CGAffineTransform c;
|
||||
CGPoint p;
|
||||
|
||||
m2c(m, &c);
|
||||
p = CGPointApplyAffineTransform(CGPointMake(*x, *y), c);
|
||||
*x = p.x;
|
||||
*y = p.y;
|
||||
}
|
||||
|
||||
void uiDrawMatrixTransformSize(uiDrawMatrix *m, double *x, double *y)
|
||||
{
|
||||
CGAffineTransform c;
|
||||
CGSize s;
|
||||
|
||||
m2c(m, &c);
|
||||
s = CGSizeApplyAffineTransform(CGSizeMake(*x, *y), c);
|
||||
*x = s.width;
|
||||
*y = s.height;
|
||||
}
|
||||
|
||||
void uiDrawTransform(uiDrawContext *c, uiDrawMatrix *m)
|
||||
{
|
||||
CGAffineTransform cm;
|
||||
|
||||
m2c(m, &cm);
|
||||
CGContextConcatCTM(c->c, cm);
|
||||
}
|
||||
|
||||
void uiDrawClip(uiDrawContext *c, uiDrawPath *path)
|
||||
{
|
||||
if (!path->ended)
|
||||
userbug("You cannot call uiDrawCilp() on a uiDrawPath that has not been ended. (path: %p)", path);
|
||||
CGContextAddPath(c->c, (CGPathRef) (path->path));
|
||||
switch (path->fillMode) {
|
||||
case uiDrawFillModeWinding:
|
||||
CGContextClip(c->c);
|
||||
break;
|
||||
case uiDrawFillModeAlternate:
|
||||
CGContextEOClip(c->c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO figure out what besides transforms these save/restore on all platforms
|
||||
void uiDrawSave(uiDrawContext *c)
|
||||
{
|
||||
CGContextSaveGState(c->c);
|
||||
}
|
||||
|
||||
void uiDrawRestore(uiDrawContext *c)
|
||||
{
|
||||
CGContextRestoreGState(c->c);
|
||||
}
|
||||
|
||||
void uiDrawText(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout)
|
||||
{
|
||||
doDrawText(c->c, c->height, x, y, layout);
|
||||
}
|
||||
Vendored
+655
@@ -0,0 +1,655 @@
|
||||
// 6 september 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// TODO
|
||||
#define complain(...) implbug(__VA_ARGS__)
|
||||
|
||||
// TODO double-check that we are properly handling allocation failures (or just toll free bridge from cocoa)
|
||||
struct uiDrawFontFamilies {
|
||||
CFArrayRef fonts;
|
||||
};
|
||||
|
||||
uiDrawFontFamilies *uiDrawListFontFamilies(void)
|
||||
{
|
||||
uiDrawFontFamilies *ff;
|
||||
|
||||
ff = uiNew(uiDrawFontFamilies);
|
||||
ff->fonts = CTFontManagerCopyAvailableFontFamilyNames();
|
||||
if (ff->fonts == NULL)
|
||||
implbug("error getting available font names (no reason specified) (TODO)");
|
||||
return ff;
|
||||
}
|
||||
|
||||
int uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff)
|
||||
{
|
||||
return CFArrayGetCount(ff->fonts);
|
||||
}
|
||||
|
||||
char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, int n)
|
||||
{
|
||||
CFStringRef familystr;
|
||||
char *family;
|
||||
|
||||
familystr = (CFStringRef) CFArrayGetValueAtIndex(ff->fonts, n);
|
||||
// toll-free bridge
|
||||
family = uiDarwinNSStringToText((NSString *) familystr);
|
||||
// Get Rule means we do not free familystr
|
||||
return family;
|
||||
}
|
||||
|
||||
void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff)
|
||||
{
|
||||
CFRelease(ff->fonts);
|
||||
uiFree(ff);
|
||||
}
|
||||
|
||||
struct uiDrawTextFont {
|
||||
CTFontRef f;
|
||||
};
|
||||
|
||||
uiDrawTextFont *mkTextFont(CTFontRef f, BOOL retain)
|
||||
{
|
||||
uiDrawTextFont *font;
|
||||
|
||||
font = uiNew(uiDrawTextFont);
|
||||
font->f = f;
|
||||
if (retain)
|
||||
CFRetain(font->f);
|
||||
return font;
|
||||
}
|
||||
|
||||
uiDrawTextFont *mkTextFontFromNSFont(NSFont *f)
|
||||
{
|
||||
// toll-free bridging; we do retain, though
|
||||
return mkTextFont((CTFontRef) f, YES);
|
||||
}
|
||||
|
||||
static CFMutableDictionaryRef newAttrList(void)
|
||||
{
|
||||
CFMutableDictionaryRef attr;
|
||||
|
||||
attr = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||
if (attr == NULL)
|
||||
complain("error creating attribute dictionary in newAttrList()()");
|
||||
return attr;
|
||||
}
|
||||
|
||||
static void addFontFamilyAttr(CFMutableDictionaryRef attr, const char *family)
|
||||
{
|
||||
CFStringRef cfstr;
|
||||
|
||||
cfstr = CFStringCreateWithCString(NULL, family, kCFStringEncodingUTF8);
|
||||
if (cfstr == NULL)
|
||||
complain("error creating font family name CFStringRef in addFontFamilyAttr()");
|
||||
CFDictionaryAddValue(attr, kCTFontFamilyNameAttribute, cfstr);
|
||||
CFRelease(cfstr); // dictionary holds its own reference
|
||||
}
|
||||
|
||||
static void addFontSizeAttr(CFMutableDictionaryRef attr, double size)
|
||||
{
|
||||
CFNumberRef n;
|
||||
|
||||
n = CFNumberCreate(NULL, kCFNumberDoubleType, &size);
|
||||
CFDictionaryAddValue(attr, kCTFontSizeAttribute, n);
|
||||
CFRelease(n);
|
||||
}
|
||||
|
||||
#if 0
|
||||
TODO
|
||||
// See http://stackoverflow.com/questions/4810409/does-coretext-support-small-caps/4811371#4811371 and https://git.gnome.org/browse/pango/tree/pango/pangocoretext-fontmap.c for what these do
|
||||
// And fortunately, unlike the traits (see below), unmatched features are simply ignored without affecting the other features :D
|
||||
static void addFontSmallCapsAttr(CFMutableDictionaryRef attr)
|
||||
{
|
||||
CFMutableArrayRef outerArray;
|
||||
CFMutableDictionaryRef innerDict;
|
||||
CFNumberRef numType, numSelector;
|
||||
int num;
|
||||
|
||||
outerArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
|
||||
if (outerArray == NULL)
|
||||
complain("error creating outer CFArray for adding small caps attributes in addFontSmallCapsAttr()");
|
||||
|
||||
// Apple's headers say these are deprecated, but a few fonts still rely on them
|
||||
num = kLetterCaseType;
|
||||
numType = CFNumberCreate(NULL, kCFNumberIntType, &num);
|
||||
num = kSmallCapsSelector;
|
||||
numSelector = CFNumberCreate(NULL, kCFNumberIntType, &num);
|
||||
innerDict = newAttrList();
|
||||
CFDictionaryAddValue(innerDict, kCTFontFeatureTypeIdentifierKey, numType);
|
||||
CFRelease(numType);
|
||||
CFDictionaryAddValue(innerDict, kCTFontFeatureSelectorIdentifierKey, numSelector);
|
||||
CFRelease(numSelector);
|
||||
CFArrayAppendValue(outerArray, innerDict);
|
||||
CFRelease(innerDict); // and likewise for CFArray
|
||||
|
||||
// these are the non-deprecated versions of the above; some fonts have these instead
|
||||
num = kLowerCaseType;
|
||||
numType = CFNumberCreate(NULL, kCFNumberIntType, &num);
|
||||
num = kLowerCaseSmallCapsSelector;
|
||||
numSelector = CFNumberCreate(NULL, kCFNumberIntType, &num);
|
||||
innerDict = newAttrList();
|
||||
CFDictionaryAddValue(innerDict, kCTFontFeatureTypeIdentifierKey, numType);
|
||||
CFRelease(numType);
|
||||
CFDictionaryAddValue(innerDict, kCTFontFeatureSelectorIdentifierKey, numSelector);
|
||||
CFRelease(numSelector);
|
||||
CFArrayAppendValue(outerArray, innerDict);
|
||||
CFRelease(innerDict); // and likewise for CFArray
|
||||
|
||||
CFDictionaryAddValue(attr, kCTFontFeatureSettingsAttribute, outerArray);
|
||||
CFRelease(outerArray);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Named constants for these were NOT added until 10.11, and even then they were added as external symbols instead of macros, so we can't use them directly :(
|
||||
// kode54 got these for me before I had access to El Capitan; thanks to him.
|
||||
#define ourNSFontWeightUltraLight -0.800000
|
||||
#define ourNSFontWeightThin -0.600000
|
||||
#define ourNSFontWeightLight -0.400000
|
||||
#define ourNSFontWeightRegular 0.000000
|
||||
#define ourNSFontWeightMedium 0.230000
|
||||
#define ourNSFontWeightSemibold 0.300000
|
||||
#define ourNSFontWeightBold 0.400000
|
||||
#define ourNSFontWeightHeavy 0.560000
|
||||
#define ourNSFontWeightBlack 0.620000
|
||||
static const CGFloat ctWeights[] = {
|
||||
// yeah these two have their names swapped; blame Pango
|
||||
[uiDrawTextWeightThin] = ourNSFontWeightUltraLight,
|
||||
[uiDrawTextWeightUltraLight] = ourNSFontWeightThin,
|
||||
[uiDrawTextWeightLight] = ourNSFontWeightLight,
|
||||
// for this one let's go between Light and Regular
|
||||
// we're doing nearest so if there happens to be an exact value hopefully it's close enough
|
||||
[uiDrawTextWeightBook] = ourNSFontWeightLight + ((ourNSFontWeightRegular - ourNSFontWeightLight) / 2),
|
||||
[uiDrawTextWeightNormal] = ourNSFontWeightRegular,
|
||||
[uiDrawTextWeightMedium] = ourNSFontWeightMedium,
|
||||
[uiDrawTextWeightSemiBold] = ourNSFontWeightSemibold,
|
||||
[uiDrawTextWeightBold] = ourNSFontWeightBold,
|
||||
// for this one let's go between Bold and Heavy
|
||||
[uiDrawTextWeightUltraBold] = ourNSFontWeightBold + ((ourNSFontWeightHeavy - ourNSFontWeightBold) / 2),
|
||||
[uiDrawTextWeightHeavy] = ourNSFontWeightHeavy,
|
||||
[uiDrawTextWeightUltraHeavy] = ourNSFontWeightBlack,
|
||||
};
|
||||
|
||||
// Unfortunately there are still no named constants for these.
|
||||
// Let's just use normalized widths.
|
||||
// As far as I can tell (OS X only ships with condensed fonts, not expanded fonts; TODO), regardless of condensed or expanded, negative means condensed and positive means expanded.
|
||||
// TODO verify this is correct
|
||||
static const CGFloat ctStretches[] = {
|
||||
[uiDrawTextStretchUltraCondensed] = -1.0,
|
||||
[uiDrawTextStretchExtraCondensed] = -0.75,
|
||||
[uiDrawTextStretchCondensed] = -0.5,
|
||||
[uiDrawTextStretchSemiCondensed] = -0.25,
|
||||
[uiDrawTextStretchNormal] = 0.0,
|
||||
[uiDrawTextStretchSemiExpanded] = 0.25,
|
||||
[uiDrawTextStretchExpanded] = 0.5,
|
||||
[uiDrawTextStretchExtraExpanded] = 0.75,
|
||||
[uiDrawTextStretchUltraExpanded] = 1.0,
|
||||
};
|
||||
|
||||
struct closeness {
|
||||
CFIndex index;
|
||||
CGFloat weight;
|
||||
CGFloat italic;
|
||||
CGFloat stretch;
|
||||
CGFloat distance;
|
||||
};
|
||||
|
||||
// Stupidity: CTFont requires an **exact match for the entire traits dictionary**, otherwise it will **drop ALL the traits**.
|
||||
// We have to implement the closest match ourselves.
|
||||
// Also we have to do this before adding the small caps flags, because the matching descriptors won't have those.
|
||||
CTFontDescriptorRef matchTraits(CTFontDescriptorRef against, uiDrawTextWeight weight, uiDrawTextItalic italic, uiDrawTextStretch stretch)
|
||||
{
|
||||
CGFloat targetWeight;
|
||||
CGFloat italicCloseness, obliqueCloseness, normalCloseness;
|
||||
CGFloat targetStretch;
|
||||
CFArrayRef matching;
|
||||
CFIndex i, n;
|
||||
struct closeness *closeness;
|
||||
CTFontDescriptorRef current;
|
||||
CTFontDescriptorRef out;
|
||||
|
||||
targetWeight = ctWeights[weight];
|
||||
switch (italic) {
|
||||
case uiDrawTextItalicNormal:
|
||||
italicCloseness = 1;
|
||||
obliqueCloseness = 1;
|
||||
normalCloseness = 0;
|
||||
break;
|
||||
case uiDrawTextItalicOblique:
|
||||
italicCloseness = 0.5;
|
||||
obliqueCloseness = 0;
|
||||
normalCloseness = 1;
|
||||
break;
|
||||
case uiDrawTextItalicItalic:
|
||||
italicCloseness = 0;
|
||||
obliqueCloseness = 0.5;
|
||||
normalCloseness = 1;
|
||||
break;
|
||||
}
|
||||
targetStretch = ctStretches[stretch];
|
||||
|
||||
matching = CTFontDescriptorCreateMatchingFontDescriptors(against, NULL);
|
||||
if (matching == NULL)
|
||||
// no matches; give the original back and hope for the best
|
||||
return against;
|
||||
n = CFArrayGetCount(matching);
|
||||
if (n == 0) {
|
||||
// likewise
|
||||
CFRelease(matching);
|
||||
return against;
|
||||
}
|
||||
|
||||
closeness = (struct closeness *) uiAlloc(n * sizeof (struct closeness), "struct closeness[]");
|
||||
for (i = 0; i < n; i++) {
|
||||
CFDictionaryRef traits;
|
||||
CFNumberRef cfnum;
|
||||
CTFontSymbolicTraits symbolic;
|
||||
|
||||
closeness[i].index = i;
|
||||
|
||||
current = CFArrayGetValueAtIndex(matching, i);
|
||||
traits = CTFontDescriptorCopyAttribute(current, kCTFontTraitsAttribute);
|
||||
if (traits == NULL) {
|
||||
// couldn't get traits; be safe by ranking it lowest
|
||||
// LONGTERM figure out what the longest possible distances are
|
||||
closeness[i].weight = 3;
|
||||
closeness[i].italic = 2;
|
||||
closeness[i].stretch = 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
symbolic = 0; // assume no symbolic traits if none are listed
|
||||
cfnum = CFDictionaryGetValue(traits, kCTFontSymbolicTrait);
|
||||
if (cfnum != NULL) {
|
||||
SInt32 s;
|
||||
|
||||
if (CFNumberGetValue(cfnum, kCFNumberSInt32Type, &s) == false)
|
||||
complain("error getting symbolic traits in matchTraits()");
|
||||
symbolic = (CTFontSymbolicTraits) s;
|
||||
// Get rule; do not release cfnum
|
||||
}
|
||||
|
||||
// now try weight
|
||||
cfnum = CFDictionaryGetValue(traits, kCTFontWeightTrait);
|
||||
if (cfnum != NULL) {
|
||||
CGFloat val;
|
||||
|
||||
// LONGTERM instead of complaining for this and width and possibly also symbolic traits above, should we just fall through to the default?
|
||||
if (CFNumberGetValue(cfnum, kCFNumberCGFloatType, &val) == false)
|
||||
complain("error getting weight value in matchTraits()");
|
||||
closeness[i].weight = val - targetWeight;
|
||||
} else
|
||||
// okay there's no weight key; let's try the literal meaning of the symbolic constant
|
||||
// LONGTERM is the weight key guaranteed?
|
||||
if ((symbolic & kCTFontBoldTrait) != 0)
|
||||
closeness[i].weight = ourNSFontWeightBold - targetWeight;
|
||||
else
|
||||
closeness[i].weight = ourNSFontWeightRegular - targetWeight;
|
||||
|
||||
// italics is a bit harder because Core Text doesn't expose a concept of obliqueness
|
||||
// Pango just does a g_strrstr() (backwards case-sensitive search) for "Oblique" in the font's style name (see https://git.gnome.org/browse/pango/tree/pango/pangocoretext-fontmap.c); let's do that too I guess
|
||||
if ((symbolic & kCTFontItalicTrait) != 0)
|
||||
closeness[i].italic = italicCloseness;
|
||||
else {
|
||||
CFStringRef styleName;
|
||||
BOOL isOblique;
|
||||
|
||||
isOblique = NO; // default value
|
||||
styleName = CTFontDescriptorCopyAttribute(current, kCTFontStyleNameAttribute);
|
||||
if (styleName != NULL) {
|
||||
CFRange range;
|
||||
|
||||
// note the use of the toll-free bridge for the string literal, since CFSTR() *can* return NULL
|
||||
range = CFStringFind(styleName, (CFStringRef) @"Oblique", kCFCompareBackwards);
|
||||
if (range.location != kCFNotFound)
|
||||
isOblique = YES;
|
||||
CFRelease(styleName);
|
||||
}
|
||||
if (isOblique)
|
||||
closeness[i].italic = obliqueCloseness;
|
||||
else
|
||||
closeness[i].italic = normalCloseness;
|
||||
}
|
||||
|
||||
// now try width
|
||||
// TODO this does not seem to be enough for Skia's extended variants; the width trait is 0 but the Expanded flag is on
|
||||
// TODO verify the rest of this matrix (what matrix?)
|
||||
cfnum = CFDictionaryGetValue(traits, kCTFontWidthTrait);
|
||||
if (cfnum != NULL) {
|
||||
CGFloat val;
|
||||
|
||||
if (CFNumberGetValue(cfnum, kCFNumberCGFloatType, &val) == false)
|
||||
complain("error getting width value in matchTraits()");
|
||||
closeness[i].stretch = val - targetStretch;
|
||||
} else
|
||||
// okay there's no width key; let's try the literal meaning of the symbolic constant
|
||||
// LONGTERM is the width key guaranteed?
|
||||
if ((symbolic & kCTFontExpandedTrait) != 0)
|
||||
closeness[i].stretch = 1.0 - targetStretch;
|
||||
else if ((symbolic & kCTFontCondensedTrait) != 0)
|
||||
closeness[i].stretch = -1.0 - targetStretch;
|
||||
else
|
||||
closeness[i].stretch = 0.0 - targetStretch;
|
||||
|
||||
CFRelease(traits);
|
||||
}
|
||||
|
||||
// now figure out the 3-space difference between the three and sort by that
|
||||
for (i = 0; i < n; i++) {
|
||||
CGFloat weight, italic, stretch;
|
||||
|
||||
weight = closeness[i].weight;
|
||||
weight *= weight;
|
||||
italic = closeness[i].italic;
|
||||
italic *= italic;
|
||||
stretch = closeness[i].stretch;
|
||||
stretch *= stretch;
|
||||
closeness[i].distance = sqrt(weight + italic + stretch);
|
||||
}
|
||||
qsort_b(closeness, n, sizeof (struct closeness), ^(const void *aa, const void *bb) {
|
||||
const struct closeness *a = (const struct closeness *) aa;
|
||||
const struct closeness *b = (const struct closeness *) bb;
|
||||
|
||||
// via http://www.gnu.org/software/libc/manual/html_node/Comparison-Functions.html#Comparison-Functions
|
||||
// LONGTERM is this really the best way? isn't it the same as if (*a < *b) return -1; if (*a > *b) return 1; return 0; ?
|
||||
return (a->distance > b->distance) - (a->distance < b->distance);
|
||||
});
|
||||
// and the first element of the sorted array is what we want
|
||||
out = CFArrayGetValueAtIndex(matching, closeness[0].index);
|
||||
CFRetain(out); // get rule
|
||||
|
||||
// release everything
|
||||
uiFree(closeness);
|
||||
CFRelease(matching);
|
||||
// and release the original descriptor since we no longer need it
|
||||
CFRelease(against);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// Now remember what I said earlier about having to add the small caps traits after calling the above? This gets a dictionary back so we can do so.
|
||||
CFMutableDictionaryRef extractAttributes(CTFontDescriptorRef desc)
|
||||
{
|
||||
CFDictionaryRef dict;
|
||||
CFMutableDictionaryRef mdict;
|
||||
|
||||
dict = CTFontDescriptorCopyAttributes(desc);
|
||||
// this might not be mutable, so make a mutable copy
|
||||
mdict = CFDictionaryCreateMutableCopy(NULL, 0, dict);
|
||||
CFRelease(dict);
|
||||
return mdict;
|
||||
}
|
||||
|
||||
uiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc)
|
||||
{
|
||||
CTFontRef f;
|
||||
CFMutableDictionaryRef attr;
|
||||
CTFontDescriptorRef cfdesc;
|
||||
|
||||
attr = newAttrList();
|
||||
addFontFamilyAttr(attr, desc->Family);
|
||||
addFontSizeAttr(attr, desc->Size);
|
||||
|
||||
// now we have to do the traits matching, so create a descriptor, match the traits, and then get the attributes back
|
||||
cfdesc = CTFontDescriptorCreateWithAttributes(attr);
|
||||
// TODO release attr?
|
||||
cfdesc = matchTraits(cfdesc, desc->Weight, desc->Italic, desc->Stretch);
|
||||
|
||||
// specify the initial size again just to be safe
|
||||
f = CTFontCreateWithFontDescriptor(cfdesc, desc->Size, NULL);
|
||||
// TODO release cfdesc?
|
||||
|
||||
return mkTextFont(f, NO); // we hold the initial reference; no need to retain again
|
||||
}
|
||||
|
||||
void uiDrawFreeTextFont(uiDrawTextFont *font)
|
||||
{
|
||||
CFRelease(font->f);
|
||||
uiFree(font);
|
||||
}
|
||||
|
||||
uintptr_t uiDrawTextFontHandle(uiDrawTextFont *font)
|
||||
{
|
||||
return (uintptr_t) (font->f);
|
||||
}
|
||||
|
||||
void uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
// text sizes and user space points are identical:
|
||||
// - https://developer.apple.com/library/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/TypoFeatures/TextSystemFeatures.html#//apple_ref/doc/uid/TP40009459-CH6-51627-BBCCHIFF text points are 72 per inch
|
||||
// - https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Transforms/Transforms.html#//apple_ref/doc/uid/TP40003290-CH204-SW5 user space points are 72 per inch
|
||||
void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics)
|
||||
{
|
||||
metrics->Ascent = CTFontGetAscent(font->f);
|
||||
metrics->Descent = CTFontGetDescent(font->f);
|
||||
metrics->Leading = CTFontGetLeading(font->f);
|
||||
metrics->UnderlinePos = CTFontGetUnderlinePosition(font->f);
|
||||
metrics->UnderlineThickness = CTFontGetUnderlineThickness(font->f);
|
||||
}
|
||||
|
||||
struct uiDrawTextLayout {
|
||||
CFMutableAttributedStringRef mas;
|
||||
CFRange *charsToRanges;
|
||||
double width;
|
||||
};
|
||||
|
||||
uiDrawTextLayout *uiDrawNewTextLayout(const char *str, uiDrawTextFont *defaultFont, double width)
|
||||
{
|
||||
uiDrawTextLayout *layout;
|
||||
CFAttributedStringRef immutable;
|
||||
CFMutableDictionaryRef attr;
|
||||
CFStringRef backing;
|
||||
CFIndex i, j, n;
|
||||
|
||||
layout = uiNew(uiDrawTextLayout);
|
||||
|
||||
// TODO docs say we need to use a different set of key callbacks
|
||||
// TODO see if the font attribute key callbacks need to be the same
|
||||
attr = newAttrList();
|
||||
// this will retain defaultFont->f; no need to worry
|
||||
CFDictionaryAddValue(attr, kCTFontAttributeName, defaultFont->f);
|
||||
|
||||
immutable = CFAttributedStringCreate(NULL, (CFStringRef) [NSString stringWithUTF8String:str], attr);
|
||||
if (immutable == NULL)
|
||||
complain("error creating immutable attributed string in uiDrawNewTextLayout()");
|
||||
CFRelease(attr);
|
||||
|
||||
layout->mas = CFAttributedStringCreateMutableCopy(NULL, 0, immutable);
|
||||
if (layout->mas == NULL)
|
||||
complain("error creating attributed string in uiDrawNewTextLayout()");
|
||||
CFRelease(immutable);
|
||||
|
||||
uiDrawTextLayoutSetWidth(layout, width);
|
||||
|
||||
// unfortunately the CFRanges for attributes expect UTF-16 codepoints
|
||||
// we want graphemes
|
||||
// fortunately CFStringGetRangeOfComposedCharactersAtIndex() is here for us
|
||||
// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Strings/Articles/stringsClusters.html says that this does work on all multi-codepoint graphemes (despite the name), and that this is the preferred function for this particular job anyway
|
||||
backing = CFAttributedStringGetString(layout->mas);
|
||||
n = CFStringGetLength(backing);
|
||||
// allocate one extra, just to be safe
|
||||
layout->charsToRanges = (CFRange *) uiAlloc((n + 1) * sizeof (CFRange), "CFRange[]");
|
||||
i = 0;
|
||||
j = 0;
|
||||
while (i < n) {
|
||||
CFRange range;
|
||||
|
||||
range = CFStringGetRangeOfComposedCharactersAtIndex(backing, i);
|
||||
i = range.location + range.length;
|
||||
layout->charsToRanges[j] = range;
|
||||
j++;
|
||||
}
|
||||
// and set the last one
|
||||
layout->charsToRanges[j].location = i;
|
||||
layout->charsToRanges[j].length = 0;
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
void uiDrawFreeTextLayout(uiDrawTextLayout *layout)
|
||||
{
|
||||
uiFree(layout->charsToRanges);
|
||||
CFRelease(layout->mas);
|
||||
uiFree(layout);
|
||||
}
|
||||
|
||||
void uiDrawTextLayoutSetWidth(uiDrawTextLayout *layout, double width)
|
||||
{
|
||||
layout->width = width;
|
||||
}
|
||||
|
||||
struct framesetter {
|
||||
CTFramesetterRef fs;
|
||||
CFMutableDictionaryRef frameAttrib;
|
||||
CGSize extents;
|
||||
};
|
||||
|
||||
// TODO CTFrameProgression for RTL/LTR
|
||||
// TODO kCTParagraphStyleSpecifierMaximumLineSpacing, kCTParagraphStyleSpecifierMinimumLineSpacing, kCTParagraphStyleSpecifierLineSpacingAdjustment for line spacing
|
||||
static void mkFramesetter(uiDrawTextLayout *layout, struct framesetter *fs)
|
||||
{
|
||||
CFRange fitRange;
|
||||
CGFloat width;
|
||||
|
||||
fs->fs = CTFramesetterCreateWithAttributedString(layout->mas);
|
||||
if (fs->fs == NULL)
|
||||
complain("error creating CTFramesetter object in mkFramesetter()");
|
||||
|
||||
// TODO kCTFramePathWidthAttributeName?
|
||||
fs->frameAttrib = NULL;
|
||||
|
||||
width = layout->width;
|
||||
if (layout->width < 0)
|
||||
width = CGFLOAT_MAX;
|
||||
// TODO these seem to be floor()'d or truncated?
|
||||
fs->extents = CTFramesetterSuggestFrameSizeWithConstraints(fs->fs,
|
||||
CFRangeMake(0, 0),
|
||||
fs->frameAttrib,
|
||||
CGSizeMake(width, CGFLOAT_MAX),
|
||||
&fitRange); // not documented as accepting NULL
|
||||
}
|
||||
|
||||
static void freeFramesetter(struct framesetter *fs)
|
||||
{
|
||||
if (fs->frameAttrib != NULL)
|
||||
CFRelease(fs->frameAttrib);
|
||||
CFRelease(fs->fs);
|
||||
}
|
||||
|
||||
// LONGTERM allow line separation and leading to be factored into a wrapping text layout
|
||||
|
||||
// TODO reconcile differences in character wrapping on platforms
|
||||
void uiDrawTextLayoutExtents(uiDrawTextLayout *layout, double *width, double *height)
|
||||
{
|
||||
struct framesetter fs;
|
||||
|
||||
mkFramesetter(layout, &fs);
|
||||
*width = fs.extents.width;
|
||||
*height = fs.extents.height;
|
||||
freeFramesetter(&fs);
|
||||
}
|
||||
|
||||
// Core Text doesn't draw onto a flipped view correctly; we have to do this
|
||||
// see the iOS bits of the first example at https://developer.apple.com/library/mac/documentation/StringsTextFonts/Conceptual/CoreText_Programming/LayoutOperations/LayoutOperations.html#//apple_ref/doc/uid/TP40005533-CH12-SW1 (iOS is naturally flipped)
|
||||
// TODO how is this affected by the CTM?
|
||||
static void prepareContextForText(CGContextRef c, CGFloat cheight, double *y)
|
||||
{
|
||||
CGContextSaveGState(c);
|
||||
CGContextTranslateCTM(c, 0, cheight);
|
||||
CGContextScaleCTM(c, 1.0, -1.0);
|
||||
CGContextSetTextMatrix(c, CGAffineTransformIdentity);
|
||||
|
||||
// wait, that's not enough; we need to offset y values to account for our new flipping
|
||||
*y = cheight - *y;
|
||||
}
|
||||
|
||||
// TODO placement is incorrect for Helvetica
|
||||
void doDrawText(CGContextRef c, CGFloat cheight, double x, double y, uiDrawTextLayout *layout)
|
||||
{
|
||||
struct framesetter fs;
|
||||
CGRect rect;
|
||||
CGPathRef path;
|
||||
CTFrameRef frame;
|
||||
|
||||
prepareContextForText(c, cheight, &y);
|
||||
mkFramesetter(layout, &fs);
|
||||
|
||||
// oh, and since we're flipped, y is the bottom-left coordinate of the rectangle, not the top-left
|
||||
// since we are flipped, we subtract
|
||||
y -= fs.extents.height;
|
||||
|
||||
rect.origin = CGPointMake(x, y);
|
||||
rect.size = fs.extents;
|
||||
path = CGPathCreateWithRect(rect, NULL);
|
||||
|
||||
frame = CTFramesetterCreateFrame(fs.fs,
|
||||
CFRangeMake(0, 0),
|
||||
path,
|
||||
fs.frameAttrib);
|
||||
if (frame == NULL)
|
||||
complain("error creating CTFrame object in doDrawText()");
|
||||
CTFrameDraw(frame, c);
|
||||
CFRelease(frame);
|
||||
|
||||
CFRelease(path);
|
||||
|
||||
freeFramesetter(&fs);
|
||||
CGContextRestoreGState(c);
|
||||
}
|
||||
|
||||
// LONGTERM provide an equivalent to CTLineGetTypographicBounds() on uiDrawTextLayout?
|
||||
|
||||
// LONGTERM keep this for later features and documentation purposes
|
||||
#if 0
|
||||
w = CTLineGetTypographicBounds(line, &ascent, &descent, NULL);
|
||||
// though CTLineGetTypographicBounds() returns 0 on error, it also returns 0 on an empty string, so we can't reasonably check for error
|
||||
CFRelease(line);
|
||||
|
||||
// LONGTERM provide a way to get the image bounds as a separate function later
|
||||
bounds = CTLineGetImageBounds(line, c);
|
||||
// though CTLineGetImageBounds() returns CGRectNull on error, it also returns CGRectNull on an empty string, so we can't reasonably check for error
|
||||
|
||||
// CGContextSetTextPosition() positions at the baseline in the case of CTLineDraw(); we need the top-left corner instead
|
||||
CTLineGetTypographicBounds(line, &yoff, NULL, NULL);
|
||||
// remember that we're flipped, so we subtract
|
||||
y -= yoff;
|
||||
CGContextSetTextPosition(c, x, y);
|
||||
#endif
|
||||
|
||||
static CFRange charsToRange(uiDrawTextLayout *layout, int startChar, int endChar)
|
||||
{
|
||||
CFRange start, end;
|
||||
CFRange out;
|
||||
|
||||
start = layout->charsToRanges[startChar];
|
||||
end = layout->charsToRanges[endChar];
|
||||
out.location = start.location;
|
||||
out.length = end.location - start.location;
|
||||
return out;
|
||||
}
|
||||
|
||||
#define rangeToCFRange() charsToRange(layout, startChar, endChar)
|
||||
|
||||
void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, int startChar, int endChar, double r, double g, double b, double a)
|
||||
{
|
||||
CGColorSpaceRef colorspace;
|
||||
CGFloat components[4];
|
||||
CGColorRef color;
|
||||
|
||||
// for consistency with windows, use sRGB
|
||||
colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
|
||||
components[0] = r;
|
||||
components[1] = g;
|
||||
components[2] = b;
|
||||
components[3] = a;
|
||||
color = CGColorCreate(colorspace, components);
|
||||
CGColorSpaceRelease(colorspace);
|
||||
|
||||
CFAttributedStringSetAttribute(layout->mas,
|
||||
rangeToCFRange(),
|
||||
kCTForegroundColorAttributeName,
|
||||
color);
|
||||
CGColorRelease(color); // TODO safe?
|
||||
}
|
||||
Vendored
+185
@@ -0,0 +1,185 @@
|
||||
// 14 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// So why did I split uiCombobox into uiCombobox and uiEditableCombobox? Here's (90% of the; the other 10% is GTK+ events) answer:
|
||||
// When you type a value into a NSComboBox that just happens to be in the list, it will autoselect that item!
|
||||
// I can't seem to find a workaround.
|
||||
// Fortunately, there's other weird behaviors that made this split worth it.
|
||||
// And besides, selected items make little sense with editable comboboxes... you either separate or combine them with the text entry :V
|
||||
|
||||
// NSComboBoxes have no intrinsic width; we'll use the default Interface Builder width for them.
|
||||
#define comboboxWidth 96
|
||||
|
||||
@interface libui_intrinsicWidthNSComboBox : NSComboBox
|
||||
@end
|
||||
|
||||
@implementation libui_intrinsicWidthNSComboBox
|
||||
|
||||
- (NSSize)intrinsicContentSize
|
||||
{
|
||||
NSSize s;
|
||||
|
||||
s = [super intrinsicContentSize];
|
||||
s.width = comboboxWidth;
|
||||
return s;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
struct uiEditableCombobox {
|
||||
uiDarwinControl c;
|
||||
NSComboBox *cb;
|
||||
void (*onChanged)(uiEditableCombobox *, void *);
|
||||
void *onChangedData;
|
||||
};
|
||||
|
||||
@interface editableComboboxDelegateClass : NSObject<NSComboBoxDelegate> {
|
||||
struct mapTable *comboboxes;
|
||||
}
|
||||
- (void)controlTextDidChange:(NSNotification *)note;
|
||||
- (void)comboBoxSelectionDidChange:(NSNotification *)note;
|
||||
- (void)registerCombobox:(uiEditableCombobox *)c;
|
||||
- (void)unregisterCombobox:(uiEditableCombobox *)c;
|
||||
@end
|
||||
|
||||
@implementation editableComboboxDelegateClass
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
self->comboboxes = newMap();
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
mapDestroy(self->comboboxes);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void)controlTextDidChange:(NSNotification *)note
|
||||
{
|
||||
uiEditableCombobox *c;
|
||||
|
||||
c = uiEditableCombobox(mapGet(self->comboboxes, [note object]));
|
||||
(*(c->onChanged))(c, c->onChangedData);
|
||||
}
|
||||
|
||||
// the above doesn't handle when an item is selected; this will
|
||||
- (void)comboBoxSelectionDidChange:(NSNotification *)note
|
||||
{
|
||||
// except this is sent BEFORE the entry is changed, and that doesn't send the above, so
|
||||
// this is via http://stackoverflow.com/a/21059819/3408572 - it avoids the need to manage selected items
|
||||
// this still isn't perfect — I get residual changes to the same value while navigating the list — but it's good enough
|
||||
[self performSelector:@selector(controlTextDidChange:)
|
||||
withObject:note
|
||||
afterDelay:0];
|
||||
}
|
||||
|
||||
- (void)registerCombobox:(uiEditableCombobox *)c
|
||||
{
|
||||
mapSet(self->comboboxes, c->cb, c);
|
||||
[c->cb setDelegate:self];
|
||||
}
|
||||
|
||||
- (void)unregisterCombobox:(uiEditableCombobox *)c
|
||||
{
|
||||
[c->cb setDelegate:nil];
|
||||
mapDelete(self->comboboxes, c->cb);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static editableComboboxDelegateClass *comboboxDelegate = nil;
|
||||
|
||||
uiDarwinControlAllDefaultsExceptDestroy(uiEditableCombobox, cb)
|
||||
|
||||
static void uiEditableComboboxDestroy(uiControl *cc)
|
||||
{
|
||||
uiEditableCombobox *c = uiEditableCombobox(cc);
|
||||
|
||||
[comboboxDelegate unregisterCombobox:c];
|
||||
[c->cb release];
|
||||
uiFreeControl(uiControl(c));
|
||||
}
|
||||
|
||||
void uiEditableComboboxAppend(uiEditableCombobox *c, const char *text)
|
||||
{
|
||||
[c->cb addItemWithObjectValue:toNSString(text)];
|
||||
}
|
||||
|
||||
char *uiEditableComboboxText(uiEditableCombobox *c)
|
||||
{
|
||||
return uiDarwinNSStringToText([c->cb stringValue]);
|
||||
}
|
||||
|
||||
void uiEditableComboboxSetText(uiEditableCombobox *c, const char *text)
|
||||
{
|
||||
NSString *t;
|
||||
|
||||
t = toNSString(text);
|
||||
[c->cb setStringValue:t];
|
||||
// yes, let's imitate the behavior that caused uiEditableCombobox to be separate in the first place!
|
||||
// just to avoid confusion when users see an option in the list in the text field but not selected in the list
|
||||
[c->cb selectItemWithObjectValue:t];
|
||||
}
|
||||
|
||||
#if 0
|
||||
// LONGTERM
|
||||
void uiEditableComboboxSetSelected(uiEditableCombobox *c, int n)
|
||||
{
|
||||
if (c->editable) {
|
||||
// see https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ComboBox/Tasks/SettingComboBoxValue.html#//apple_ref/doc/uid/20000256
|
||||
id delegate;
|
||||
|
||||
// this triggers the delegate; turn it off for now
|
||||
delegate = [c->cb delegate];
|
||||
[c->cb setDelegate:nil];
|
||||
|
||||
// this seems to work fine for -1 too
|
||||
[c->cb selectItemAtIndex:n];
|
||||
if (n == -1)
|
||||
[c->cb setObjectValue:@""];
|
||||
else
|
||||
[c->cb setObjectValue:[c->cb objectValueOfSelectedItem]];
|
||||
|
||||
[c->cb setDelegate:delegate];
|
||||
return;
|
||||
}
|
||||
[c->pb selectItemAtIndex:n];
|
||||
}
|
||||
#endif
|
||||
|
||||
void uiEditableComboboxOnChanged(uiEditableCombobox *c, void (*f)(uiEditableCombobox *c, void *data), void *data)
|
||||
{
|
||||
c->onChanged = f;
|
||||
c->onChangedData = data;
|
||||
}
|
||||
|
||||
static void defaultOnChanged(uiEditableCombobox *c, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
uiEditableCombobox *uiNewEditableCombobox(void)
|
||||
{
|
||||
uiEditableCombobox *c;
|
||||
|
||||
uiDarwinNewControl(uiEditableCombobox, c);
|
||||
|
||||
c->cb = [[libui_intrinsicWidthNSComboBox alloc] initWithFrame:NSZeroRect];
|
||||
[c->cb setUsesDataSource:NO];
|
||||
[c->cb setButtonBordered:YES];
|
||||
[c->cb setCompletes:NO];
|
||||
uiDarwinSetControlFont(c->cb, NSRegularControlSize);
|
||||
|
||||
if (comboboxDelegate == nil) {
|
||||
comboboxDelegate = [[editableComboboxDelegateClass new] autorelease];
|
||||
[delegates addObject:comboboxDelegate];
|
||||
}
|
||||
[comboboxDelegate registerCombobox:c];
|
||||
uiEditableComboboxOnChanged(c, defaultOnChanged, NULL);
|
||||
|
||||
return c;
|
||||
}
|
||||
Vendored
+251
@@ -0,0 +1,251 @@
|
||||
// 14 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// Text fields for entering text have no intrinsic width; we'll use the default Interface Builder width for them.
|
||||
#define textfieldWidth 96
|
||||
|
||||
@interface libui_intrinsicWidthNSTextField : NSTextField
|
||||
@end
|
||||
|
||||
@implementation libui_intrinsicWidthNSTextField
|
||||
|
||||
- (NSSize)intrinsicContentSize
|
||||
{
|
||||
NSSize s;
|
||||
|
||||
s = [super intrinsicContentSize];
|
||||
s.width = textfieldWidth;
|
||||
return s;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// TODO does this have one on its own?
|
||||
@interface libui_intrinsicWidthNSSecureTextField : NSSecureTextField
|
||||
@end
|
||||
|
||||
@implementation libui_intrinsicWidthNSSecureTextField
|
||||
|
||||
- (NSSize)intrinsicContentSize
|
||||
{
|
||||
NSSize s;
|
||||
|
||||
s = [super intrinsicContentSize];
|
||||
s.width = textfieldWidth;
|
||||
return s;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// TODO does this have one on its own?
|
||||
@interface libui_intrinsicWidthNSSearchField : NSSearchField
|
||||
@end
|
||||
|
||||
@implementation libui_intrinsicWidthNSSearchField
|
||||
|
||||
- (NSSize)intrinsicContentSize
|
||||
{
|
||||
NSSize s;
|
||||
|
||||
s = [super intrinsicContentSize];
|
||||
s.width = textfieldWidth;
|
||||
return s;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
struct uiEntry {
|
||||
uiDarwinControl c;
|
||||
NSTextField *textfield;
|
||||
void (*onChanged)(uiEntry *, void *);
|
||||
void *onChangedData;
|
||||
};
|
||||
|
||||
static BOOL isSearchField(NSTextField *tf)
|
||||
{
|
||||
return [tf isKindOfClass:[NSSearchField class]];
|
||||
}
|
||||
|
||||
@interface entryDelegateClass : NSObject<NSTextFieldDelegate> {
|
||||
struct mapTable *entries;
|
||||
}
|
||||
- (void)controlTextDidChange:(NSNotification *)note;
|
||||
- (IBAction)onSearch:(id)sender;
|
||||
- (void)registerEntry:(uiEntry *)e;
|
||||
- (void)unregisterEntry:(uiEntry *)e;
|
||||
@end
|
||||
|
||||
@implementation entryDelegateClass
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
self->entries = newMap();
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
mapDestroy(self->entries);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void)controlTextDidChange:(NSNotification *)note
|
||||
{
|
||||
[self onSearch:[note object]];
|
||||
}
|
||||
|
||||
- (IBAction)onSearch:(id)sender
|
||||
{
|
||||
uiEntry *e;
|
||||
|
||||
e = (uiEntry *) mapGet(self->entries, sender);
|
||||
(*(e->onChanged))(e, e->onChangedData);
|
||||
}
|
||||
|
||||
- (void)registerEntry:(uiEntry *)e
|
||||
{
|
||||
mapSet(self->entries, e->textfield, e);
|
||||
if (isSearchField(e->textfield)) {
|
||||
[e->textfield setTarget:self];
|
||||
[e->textfield setAction:@selector(onSearch:)];
|
||||
} else
|
||||
[e->textfield setDelegate:self];
|
||||
}
|
||||
|
||||
- (void)unregisterEntry:(uiEntry *)e
|
||||
{
|
||||
if (isSearchField(e->textfield))
|
||||
[e->textfield setTarget:nil];
|
||||
else
|
||||
[e->textfield setDelegate:nil];
|
||||
mapDelete(self->entries, e->textfield);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static entryDelegateClass *entryDelegate = nil;
|
||||
|
||||
uiDarwinControlAllDefaultsExceptDestroy(uiEntry, textfield)
|
||||
|
||||
static void uiEntryDestroy(uiControl *c)
|
||||
{
|
||||
uiEntry *e = uiEntry(c);
|
||||
|
||||
[entryDelegate unregisterEntry:e];
|
||||
[e->textfield release];
|
||||
uiFreeControl(uiControl(e));
|
||||
}
|
||||
|
||||
char *uiEntryText(uiEntry *e)
|
||||
{
|
||||
return uiDarwinNSStringToText([e->textfield stringValue]);
|
||||
}
|
||||
|
||||
void uiEntrySetText(uiEntry *e, const char *text)
|
||||
{
|
||||
[e->textfield setStringValue:toNSString(text)];
|
||||
// don't queue the control for resize; entry sizes are independent of their contents
|
||||
}
|
||||
|
||||
void uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *, void *), void *data)
|
||||
{
|
||||
e->onChanged = f;
|
||||
e->onChangedData = data;
|
||||
}
|
||||
|
||||
int uiEntryReadOnly(uiEntry *e)
|
||||
{
|
||||
return [e->textfield isEditable] == NO;
|
||||
}
|
||||
|
||||
void uiEntrySetReadOnly(uiEntry *e, int readonly)
|
||||
{
|
||||
BOOL editable;
|
||||
|
||||
editable = YES;
|
||||
if (readonly)
|
||||
editable = NO;
|
||||
[e->textfield setEditable:editable];
|
||||
}
|
||||
|
||||
static void defaultOnChanged(uiEntry *e, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
// these are based on interface builder defaults; my comments in the old code weren't very good so I don't really know what talked about what, sorry :/
|
||||
void finishNewTextField(NSTextField *t, BOOL isEntry)
|
||||
{
|
||||
uiDarwinSetControlFont(t, NSRegularControlSize);
|
||||
|
||||
// THE ORDER OF THESE CALLS IS IMPORTANT; CHANGE IT AND THE BORDERS WILL DISAPPEAR
|
||||
[t setBordered:NO];
|
||||
[t setBezelStyle:NSTextFieldSquareBezel];
|
||||
[t setBezeled:isEntry];
|
||||
|
||||
// we don't need to worry about substitutions/autocorrect here; see window_darwin.m for details
|
||||
|
||||
[[t cell] setLineBreakMode:NSLineBreakByClipping];
|
||||
[[t cell] setScrollable:YES];
|
||||
}
|
||||
|
||||
static NSTextField *realNewEditableTextField(Class class)
|
||||
{
|
||||
NSTextField *tf;
|
||||
|
||||
tf = [[class alloc] initWithFrame:NSZeroRect];
|
||||
[tf setSelectable:YES]; // otherwise the setting is masked by the editable default of YES
|
||||
finishNewTextField(tf, YES);
|
||||
return tf;
|
||||
}
|
||||
|
||||
NSTextField *newEditableTextField(void)
|
||||
{
|
||||
return realNewEditableTextField([libui_intrinsicWidthNSTextField class]);
|
||||
}
|
||||
|
||||
static uiEntry *finishNewEntry(Class class)
|
||||
{
|
||||
uiEntry *e;
|
||||
|
||||
uiDarwinNewControl(uiEntry, e);
|
||||
|
||||
e->textfield = realNewEditableTextField(class);
|
||||
|
||||
if (entryDelegate == nil) {
|
||||
entryDelegate = [[entryDelegateClass new] autorelease];
|
||||
[delegates addObject:entryDelegate];
|
||||
}
|
||||
[entryDelegate registerEntry:e];
|
||||
uiEntryOnChanged(e, defaultOnChanged, NULL);
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
uiEntry *uiNewEntry(void)
|
||||
{
|
||||
return finishNewEntry([libui_intrinsicWidthNSTextField class]);
|
||||
}
|
||||
|
||||
uiEntry *uiNewPasswordEntry(void)
|
||||
{
|
||||
return finishNewEntry([libui_intrinsicWidthNSSecureTextField class]);
|
||||
}
|
||||
|
||||
uiEntry *uiNewSearchEntry(void)
|
||||
{
|
||||
uiEntry *e;
|
||||
NSSearchField *s;
|
||||
|
||||
e = finishNewEntry([libui_intrinsicWidthNSSearchField class]);
|
||||
s = (NSSearchField *) (e->textfield);
|
||||
// TODO these are only on 10.10
|
||||
// [s setSendsSearchStringImmediately:NO];
|
||||
// [s setSendsWholeSearchString:NO];
|
||||
[s setBordered:NO];
|
||||
[s setBezelStyle:NSTextFieldRoundedBezel];
|
||||
[s setBezeled:YES];
|
||||
return e;
|
||||
}
|
||||
Vendored
+218
@@ -0,0 +1,218 @@
|
||||
// 14 april 2016
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
@interface fontButton : NSButton {
|
||||
uiFontButton *libui_b;
|
||||
NSFont *libui_font;
|
||||
}
|
||||
- (id)initWithFrame:(NSRect)frame libuiFontButton:(uiFontButton *)b;
|
||||
- (void)updateFontButtonLabel;
|
||||
- (IBAction)fontButtonClicked:(id)sender;
|
||||
- (void)activateFontButton;
|
||||
- (void)deactivateFontButton:(BOOL)activatingAnother;
|
||||
- (void)deactivateOnClose:(NSNotification *)note;
|
||||
- (uiDrawTextFont *)libuiFont;
|
||||
@end
|
||||
|
||||
// only one may be active at one time
|
||||
static fontButton *activeFontButton = nil;
|
||||
|
||||
struct uiFontButton {
|
||||
uiDarwinControl c;
|
||||
fontButton *button;
|
||||
void (*onChanged)(uiFontButton *, void *);
|
||||
void *onChangedData;
|
||||
};
|
||||
|
||||
@implementation fontButton
|
||||
|
||||
- (id)initWithFrame:(NSRect)frame libuiFontButton:(uiFontButton *)b
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self->libui_b = b;
|
||||
|
||||
// imitate a NSColorWell in appearance
|
||||
[self setButtonType:NSPushOnPushOffButton];
|
||||
[self setBordered:YES];
|
||||
[self setBezelStyle:NSShadowlessSquareBezelStyle];
|
||||
|
||||
// default font values according to the CTFontDescriptor reference
|
||||
// this is autoreleased (thanks swillits in irc.freenode.net/#macdev)
|
||||
self->libui_font = [[NSFont fontWithName:@"Helvetica" size:12.0] retain];
|
||||
[self updateFontButtonLabel];
|
||||
|
||||
// for when clicked
|
||||
[self setTarget:self];
|
||||
[self setAction:@selector(fontButtonClicked:)];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
// clean up notifications
|
||||
if (activeFontButton == self)
|
||||
[self deactivateFontButton:NO];
|
||||
[self->libui_font release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void)updateFontButtonLabel
|
||||
{
|
||||
NSString *title;
|
||||
|
||||
title = [NSString stringWithFormat:@"%@ %g",
|
||||
[self->libui_font displayName],
|
||||
[self->libui_font pointSize]];
|
||||
[self setTitle:title];
|
||||
}
|
||||
|
||||
- (IBAction)fontButtonClicked:(id)sender
|
||||
{
|
||||
if ([self state] == NSOnState)
|
||||
[self activateFontButton];
|
||||
else
|
||||
[self deactivateFontButton:NO];
|
||||
}
|
||||
|
||||
- (void)activateFontButton
|
||||
{
|
||||
NSFontManager *sfm;
|
||||
|
||||
sfm = [NSFontManager sharedFontManager];
|
||||
if (activeFontButton != nil)
|
||||
[activeFontButton deactivateFontButton:YES];
|
||||
[sfm setTarget:self];
|
||||
[sfm setSelectedFont:self->libui_font isMultiple:NO];
|
||||
[sfm orderFrontFontPanel:self];
|
||||
activeFontButton = self;
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(deactivateOnClose:)
|
||||
name:NSWindowWillCloseNotification
|
||||
object:[NSFontPanel sharedFontPanel]];
|
||||
[self setState:NSOnState];
|
||||
}
|
||||
|
||||
- (void)deactivateFontButton:(BOOL)activatingAnother
|
||||
{
|
||||
NSFontManager *sfm;
|
||||
|
||||
sfm = [NSFontManager sharedFontManager];
|
||||
[sfm setTarget:nil];
|
||||
if (!activatingAnother)
|
||||
[[NSFontPanel sharedFontPanel] orderOut:self];
|
||||
activeFontButton = nil;
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:NSWindowWillCloseNotification
|
||||
object:[NSFontPanel sharedFontPanel]];
|
||||
[self setState:NSOffState];
|
||||
}
|
||||
|
||||
- (void)deactivateOnClose:(NSNotification *)note
|
||||
{
|
||||
[self deactivateFontButton:NO];
|
||||
}
|
||||
|
||||
- (void)changeFont:(id)sender
|
||||
{
|
||||
NSFontManager *fm;
|
||||
NSFont *old;
|
||||
uiFontButton *b = self->libui_b;
|
||||
|
||||
fm = (NSFontManager *) sender;
|
||||
old = self->libui_font;
|
||||
self->libui_font = [sender convertFont:self->libui_font];
|
||||
// do this even if it returns the same; we don't own anything that isn't from a new or alloc/init
|
||||
[self->libui_font retain];
|
||||
// do this second just in case
|
||||
[old release];
|
||||
[self updateFontButtonLabel];
|
||||
(*(b->onChanged))(b, b->onChangedData);
|
||||
}
|
||||
|
||||
- (NSUInteger)validModesForFontPanel:(NSFontPanel *)panel
|
||||
{
|
||||
return NSFontPanelFaceModeMask |
|
||||
NSFontPanelSizeModeMask |
|
||||
NSFontPanelCollectionModeMask;
|
||||
}
|
||||
|
||||
- (uiDrawTextFont *)libuiFont
|
||||
{
|
||||
return mkTextFontFromNSFont(self->libui_font);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
uiDarwinControlAllDefaults(uiFontButton, button)
|
||||
|
||||
// we do not want font change events to be sent to any controls other than the font buttons
|
||||
// see main.m for more details
|
||||
BOOL fontButtonInhibitSendAction(SEL sel, id from, id to)
|
||||
{
|
||||
if (sel != @selector(changeFont:))
|
||||
return NO;
|
||||
return ![to isKindOfClass:[fontButton class]];
|
||||
}
|
||||
|
||||
// we do not want NSFontPanelValidation messages to be sent to any controls other than the font buttons when a font button is active
|
||||
// see main.m for more details
|
||||
BOOL fontButtonOverrideTargetForAction(SEL sel, id from, id to, id *override)
|
||||
{
|
||||
if (activeFontButton == nil)
|
||||
return NO;
|
||||
if (sel != @selector(validModesForFontPanel:))
|
||||
return NO;
|
||||
*override = activeFontButton;
|
||||
return YES;
|
||||
}
|
||||
|
||||
// we also don't want the panel to be usable when there's a dialog running; see stddialogs.m for more details on that
|
||||
// unfortunately the panel seems to ignore -setWorksWhenModal: so we'll have to do things ourselves
|
||||
@interface nonModalFontPanel : NSFontPanel
|
||||
@end
|
||||
|
||||
@implementation nonModalFontPanel
|
||||
|
||||
- (BOOL)worksWhenModal
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
void setupFontPanel(void)
|
||||
{
|
||||
[NSFontManager setFontPanelFactory:[nonModalFontPanel class]];
|
||||
}
|
||||
|
||||
static void defaultOnChanged(uiFontButton *b, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
uiDrawTextFont *uiFontButtonFont(uiFontButton *b)
|
||||
{
|
||||
return [b->button libuiFont];
|
||||
}
|
||||
|
||||
void uiFontButtonOnChanged(uiFontButton *b, void (*f)(uiFontButton *, void *), void *data)
|
||||
{
|
||||
b->onChanged = f;
|
||||
b->onChangedData = data;
|
||||
}
|
||||
|
||||
uiFontButton *uiNewFontButton(void)
|
||||
{
|
||||
uiFontButton *b;
|
||||
|
||||
uiDarwinNewControl(uiFontButton, b);
|
||||
|
||||
b->button = [[fontButton alloc] initWithFrame:NSZeroRect libuiFontButton:b];
|
||||
uiDarwinSetControlFont(b->button, NSRegularControlSize);
|
||||
|
||||
uiFontButtonOnChanged(b, defaultOnChanged, NULL);
|
||||
|
||||
return b;
|
||||
}
|
||||
Vendored
+561
@@ -0,0 +1,561 @@
|
||||
// 7 june 2016
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// TODO in the test program, sometimes one of the radio buttons can disappear (try when spaced)
|
||||
|
||||
@interface formChild : NSView
|
||||
@property uiControl *c;
|
||||
@property (strong) NSTextField *label;
|
||||
@property BOOL stretchy;
|
||||
@property NSLayoutPriority oldHorzHuggingPri;
|
||||
@property NSLayoutPriority oldVertHuggingPri;
|
||||
@property (strong) NSLayoutConstraint *baseline;
|
||||
@property (strong) NSLayoutConstraint *leading;
|
||||
@property (strong) NSLayoutConstraint *top;
|
||||
@property (strong) NSLayoutConstraint *trailing;
|
||||
@property (strong) NSLayoutConstraint *bottom;
|
||||
- (id)initWithLabel:(NSTextField *)l;
|
||||
- (void)onDestroy;
|
||||
- (NSView *)view;
|
||||
@end
|
||||
|
||||
@interface formView : NSView {
|
||||
uiForm *f;
|
||||
NSMutableArray *children;
|
||||
int padded;
|
||||
|
||||
NSLayoutConstraint *first;
|
||||
NSMutableArray *inBetweens;
|
||||
NSLayoutConstraint *last;
|
||||
NSMutableArray *widths;
|
||||
NSMutableArray *leadings;
|
||||
NSMutableArray *middles;
|
||||
NSMutableArray *trailings;
|
||||
}
|
||||
- (id)initWithF:(uiForm *)ff;
|
||||
- (void)onDestroy;
|
||||
- (void)removeOurConstraints;
|
||||
- (void)syncEnableStates:(int)enabled;
|
||||
- (CGFloat)paddingAmount;
|
||||
- (void)establishOurConstraints;
|
||||
- (void)append:(NSString *)label c:(uiControl *)c stretchy:(int)stretchy;
|
||||
- (void)delete:(int)n;
|
||||
- (int)isPadded;
|
||||
- (void)setPadded:(int)p;
|
||||
- (BOOL)hugsTrailing;
|
||||
- (BOOL)hugsBottom;
|
||||
- (int)nStretchy;
|
||||
@end
|
||||
|
||||
struct uiForm {
|
||||
uiDarwinControl c;
|
||||
formView *view;
|
||||
};
|
||||
|
||||
@implementation formChild
|
||||
|
||||
- (id)initWithLabel:(NSTextField *)l
|
||||
{
|
||||
self = [super initWithFrame:NSZeroRect];
|
||||
if (self) {
|
||||
self.label = l;
|
||||
[self.label setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self.label setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal];
|
||||
[self.label setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationVertical];
|
||||
[self.label setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal];
|
||||
[self.label setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationVertical];
|
||||
[self addSubview:self.label];
|
||||
|
||||
self.leading = mkConstraint(self.label, NSLayoutAttributeLeading,
|
||||
NSLayoutRelationGreaterThanOrEqual,
|
||||
self, NSLayoutAttributeLeading,
|
||||
1, 0,
|
||||
@"uiForm label leading");
|
||||
[self addConstraint:self.leading];
|
||||
self.top = mkConstraint(self.label, NSLayoutAttributeTop,
|
||||
NSLayoutRelationEqual,
|
||||
self, NSLayoutAttributeTop,
|
||||
1, 0,
|
||||
@"uiForm label top");
|
||||
[self addConstraint:self.top];
|
||||
self.trailing = mkConstraint(self.label, NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationEqual,
|
||||
self, NSLayoutAttributeTrailing,
|
||||
1, 0,
|
||||
@"uiForm label trailing");
|
||||
[self addConstraint:self.trailing];
|
||||
self.bottom = mkConstraint(self.label, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationEqual,
|
||||
self, NSLayoutAttributeBottom,
|
||||
1, 0,
|
||||
@"uiForm label bottom");
|
||||
[self addConstraint:self.bottom];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)onDestroy
|
||||
{
|
||||
[self removeConstraint:self.trailing];
|
||||
self.trailing = nil;
|
||||
[self removeConstraint:self.top];
|
||||
self.top = nil;
|
||||
[self removeConstraint:self.bottom];
|
||||
self.bottom = nil;
|
||||
|
||||
[self.label removeFromSuperview];
|
||||
self.label = nil;
|
||||
}
|
||||
|
||||
- (NSView *)view
|
||||
{
|
||||
return (NSView *) uiControlHandle(self.c);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation formView
|
||||
|
||||
- (id)initWithF:(uiForm *)ff
|
||||
{
|
||||
self = [super initWithFrame:NSZeroRect];
|
||||
if (self != nil) {
|
||||
self->f = ff;
|
||||
self->padded = 0;
|
||||
self->children = [NSMutableArray new];
|
||||
|
||||
self->inBetweens = [NSMutableArray new];
|
||||
self->widths = [NSMutableArray new];
|
||||
self->leadings = [NSMutableArray new];
|
||||
self->middles = [NSMutableArray new];
|
||||
self->trailings = [NSMutableArray new];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)onDestroy
|
||||
{
|
||||
formChild *fc;
|
||||
|
||||
[self removeOurConstraints];
|
||||
[self->inBetweens release];
|
||||
[self->widths release];
|
||||
[self->leadings release];
|
||||
[self->middles release];
|
||||
[self->trailings release];
|
||||
|
||||
for (fc in self->children) {
|
||||
[self removeConstraint:fc.baseline];
|
||||
fc.baseline = nil;
|
||||
uiControlSetParent(fc.c, NULL);
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(fc.c), nil);
|
||||
uiControlDestroy(fc.c);
|
||||
[fc onDestroy];
|
||||
[fc removeFromSuperview];
|
||||
}
|
||||
[self->children release];
|
||||
}
|
||||
|
||||
- (void)removeOurConstraints
|
||||
{
|
||||
if (self->first != nil) {
|
||||
[self removeConstraint:self->first];
|
||||
[self->first release];
|
||||
self->first = nil;
|
||||
}
|
||||
if ([self->inBetweens count] != 0) {
|
||||
[self removeConstraints:self->inBetweens];
|
||||
[self->inBetweens removeAllObjects];
|
||||
}
|
||||
if (self->last != nil) {
|
||||
[self removeConstraint:self->last];
|
||||
[self->last release];
|
||||
self->last = nil;
|
||||
}
|
||||
if ([self->widths count] != 0) {
|
||||
[self removeConstraints:self->widths];
|
||||
[self->widths removeAllObjects];
|
||||
}
|
||||
if ([self->leadings count] != 0) {
|
||||
[self removeConstraints:self->leadings];
|
||||
[self->leadings removeAllObjects];
|
||||
}
|
||||
if ([self->middles count] != 0) {
|
||||
[self removeConstraints:self->middles];
|
||||
[self->middles removeAllObjects];
|
||||
}
|
||||
if ([self->trailings count] != 0) {
|
||||
[self removeConstraints:self->trailings];
|
||||
[self->trailings removeAllObjects];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)syncEnableStates:(int)enabled
|
||||
{
|
||||
formChild *fc;
|
||||
|
||||
for (fc in self->children)
|
||||
uiDarwinControlSyncEnableState(uiDarwinControl(fc.c), enabled);
|
||||
}
|
||||
|
||||
- (CGFloat)paddingAmount
|
||||
{
|
||||
if (!self->padded)
|
||||
return 0.0;
|
||||
return uiDarwinPaddingAmount(NULL);
|
||||
}
|
||||
|
||||
- (void)establishOurConstraints
|
||||
{
|
||||
formChild *fc;
|
||||
CGFloat padding;
|
||||
NSView *prev, *prevlabel;
|
||||
NSLayoutConstraint *c;
|
||||
|
||||
[self removeOurConstraints];
|
||||
if ([self->children count] == 0)
|
||||
return;
|
||||
padding = [self paddingAmount];
|
||||
|
||||
// first arrange the children vertically and make them the same width
|
||||
prev = nil;
|
||||
for (fc in self->children) {
|
||||
[fc setHidden:!uiControlVisible(fc.c)];
|
||||
if (!uiControlVisible(fc.c))
|
||||
continue;
|
||||
if (prev == nil) { // first view
|
||||
self->first = mkConstraint(self, NSLayoutAttributeTop,
|
||||
NSLayoutRelationEqual,
|
||||
[fc view], NSLayoutAttributeTop,
|
||||
1, 0,
|
||||
@"uiForm first vertical constraint");
|
||||
[self addConstraint:self->first];
|
||||
[self->first retain];
|
||||
prev = [fc view];
|
||||
prevlabel = fc;
|
||||
continue;
|
||||
}
|
||||
// not the first; link it
|
||||
c = mkConstraint(prev, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationEqual,
|
||||
[fc view], NSLayoutAttributeTop,
|
||||
1, -padding,
|
||||
@"uiForm in-between vertical constraint");
|
||||
[self addConstraint:c];
|
||||
[self->inBetweens addObject:c];
|
||||
// and make the same width
|
||||
c = mkConstraint(prev, NSLayoutAttributeWidth,
|
||||
NSLayoutRelationEqual,
|
||||
[fc view], NSLayoutAttributeWidth,
|
||||
1, 0,
|
||||
@"uiForm control width constraint");
|
||||
[self addConstraint:c];
|
||||
[self->widths addObject:c];
|
||||
c = mkConstraint(prevlabel, NSLayoutAttributeWidth,
|
||||
NSLayoutRelationEqual,
|
||||
fc, NSLayoutAttributeWidth,
|
||||
1, 0,
|
||||
@"uiForm label lwidth constraint");
|
||||
[self addConstraint:c];
|
||||
[self->widths addObject:c];
|
||||
prev = [fc view];
|
||||
prevlabel = fc;
|
||||
}
|
||||
if (prev == nil) // all hidden; act as if nothing there
|
||||
return;
|
||||
self->last = mkConstraint(prev, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationEqual,
|
||||
self, NSLayoutAttributeBottom,
|
||||
1, 0,
|
||||
@"uiForm last vertical constraint");
|
||||
[self addConstraint:self->last];
|
||||
[self->last retain];
|
||||
|
||||
// now arrange the controls horizontally
|
||||
for (fc in self->children) {
|
||||
if (!uiControlVisible(fc.c))
|
||||
continue;
|
||||
c = mkConstraint(self, NSLayoutAttributeLeading,
|
||||
NSLayoutRelationEqual,
|
||||
fc, NSLayoutAttributeLeading,
|
||||
1, 0,
|
||||
@"uiForm leading constraint");
|
||||
[self addConstraint:c];
|
||||
[self->leadings addObject:c];
|
||||
// coerce the control to be as wide as possible
|
||||
// see http://stackoverflow.com/questions/37710892/in-auto-layout-i-set-up-labels-that-shouldnt-grow-horizontally-and-controls-th
|
||||
c = mkConstraint(self, NSLayoutAttributeLeading,
|
||||
NSLayoutRelationEqual,
|
||||
[fc view], NSLayoutAttributeLeading,
|
||||
1, 0,
|
||||
@"uiForm leading constraint");
|
||||
[c setPriority:NSLayoutPriorityDefaultHigh];
|
||||
[self addConstraint:c];
|
||||
[self->leadings addObject:c];
|
||||
c = mkConstraint(fc, NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationEqual,
|
||||
[fc view], NSLayoutAttributeLeading,
|
||||
1, -padding,
|
||||
@"uiForm middle constraint");
|
||||
[self addConstraint:c];
|
||||
[self->middles addObject:c];
|
||||
c = mkConstraint([fc view], NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationEqual,
|
||||
self, NSLayoutAttributeTrailing,
|
||||
1, 0,
|
||||
@"uiForm trailing constraint");
|
||||
[self addConstraint:c];
|
||||
[self->trailings addObject:c];
|
||||
// TODO
|
||||
c = mkConstraint(fc, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationLessThanOrEqual,
|
||||
self, NSLayoutAttributeBottom,
|
||||
1, 0,
|
||||
@"TODO");
|
||||
[self addConstraint:c];
|
||||
[self->trailings addObject:c];
|
||||
}
|
||||
|
||||
// and make all stretchy controls have the same height
|
||||
prev = nil;
|
||||
for (fc in self->children) {
|
||||
if (!uiControlVisible(fc.c))
|
||||
continue;
|
||||
if (!fc.stretchy)
|
||||
continue;
|
||||
if (prev == nil) {
|
||||
prev = [fc view];
|
||||
continue;
|
||||
}
|
||||
c = mkConstraint([fc view], NSLayoutAttributeHeight,
|
||||
NSLayoutRelationEqual,
|
||||
prev, NSLayoutAttributeHeight,
|
||||
1, 0,
|
||||
@"uiForm stretchy constraint");
|
||||
[self addConstraint:c];
|
||||
// TODO make a dedicated array for this
|
||||
[self->leadings addObject:c];
|
||||
}
|
||||
|
||||
// we don't arrange the labels vertically; that's done when we add the control since those constraints don't need to change (they just need to be at their baseline)
|
||||
}
|
||||
|
||||
- (void)append:(NSString *)label c:(uiControl *)c stretchy:(int)stretchy
|
||||
{
|
||||
formChild *fc;
|
||||
NSLayoutPriority priority;
|
||||
NSLayoutAttribute attribute;
|
||||
int oldnStretchy;
|
||||
|
||||
fc = [[formChild alloc] initWithLabel:newLabel(label)];
|
||||
fc.c = c;
|
||||
fc.stretchy = stretchy;
|
||||
fc.oldHorzHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(fc.c), NSLayoutConstraintOrientationHorizontal);
|
||||
fc.oldVertHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(fc.c), NSLayoutConstraintOrientationVertical);
|
||||
[fc setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self addSubview:fc];
|
||||
|
||||
uiControlSetParent(fc.c, uiControl(self->f));
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(fc.c), self);
|
||||
uiDarwinControlSyncEnableState(uiDarwinControl(fc.c), uiControlEnabledToUser(uiControl(self->f)));
|
||||
|
||||
// if a control is stretchy, it should not hug vertically
|
||||
// otherwise, it should *forcibly* hug
|
||||
if (fc.stretchy)
|
||||
priority = NSLayoutPriorityDefaultLow;
|
||||
else
|
||||
// LONGTERM will default high work?
|
||||
priority = NSLayoutPriorityRequired;
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(fc.c), priority, NSLayoutConstraintOrientationVertical);
|
||||
// make sure controls don't hug their horizontal direction so they fill the width of the view
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(fc.c), NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationHorizontal);
|
||||
|
||||
// and constrain the baselines to position the label vertically
|
||||
// if the view is a scroll view, align tops, not baselines
|
||||
// this is what Interface Builder does
|
||||
attribute = NSLayoutAttributeBaseline;
|
||||
if ([[fc view] isKindOfClass:[NSScrollView class]])
|
||||
attribute = NSLayoutAttributeTop;
|
||||
fc.baseline = mkConstraint(fc.label, attribute,
|
||||
NSLayoutRelationEqual,
|
||||
[fc view], attribute,
|
||||
1, 0,
|
||||
@"uiForm baseline constraint");
|
||||
[self addConstraint:fc.baseline];
|
||||
|
||||
oldnStretchy = [self nStretchy];
|
||||
[self->children addObject:fc];
|
||||
|
||||
[self establishOurConstraints];
|
||||
if (fc.stretchy)
|
||||
if (oldnStretchy == 0)
|
||||
uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->f));
|
||||
|
||||
[fc release]; // we don't need the initial reference now
|
||||
}
|
||||
|
||||
- (void)delete:(int)n
|
||||
{
|
||||
formChild *fc;
|
||||
int stretchy;
|
||||
|
||||
fc = (formChild *) [self->children objectAtIndex:n];
|
||||
stretchy = fc.stretchy;
|
||||
|
||||
uiControlSetParent(fc.c, NULL);
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(fc.c), nil);
|
||||
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(fc.c), fc.oldHorzHuggingPri, NSLayoutConstraintOrientationHorizontal);
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(fc.c), fc.oldVertHuggingPri, NSLayoutConstraintOrientationVertical);
|
||||
|
||||
[fc onDestroy];
|
||||
[self->children removeObjectAtIndex:n];
|
||||
|
||||
[self establishOurConstraints];
|
||||
if (stretchy)
|
||||
if ([self nStretchy] == 0)
|
||||
uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->f));
|
||||
}
|
||||
|
||||
- (int)isPadded
|
||||
{
|
||||
return self->padded;
|
||||
}
|
||||
|
||||
- (void)setPadded:(int)p
|
||||
{
|
||||
CGFloat padding;
|
||||
NSLayoutConstraint *c;
|
||||
|
||||
self->padded = p;
|
||||
padding = [self paddingAmount];
|
||||
for (c in self->inBetweens)
|
||||
[c setConstant:-padding];
|
||||
for (c in self->middles)
|
||||
[c setConstant:-padding];
|
||||
}
|
||||
|
||||
- (BOOL)hugsTrailing
|
||||
{
|
||||
return YES; // always hug trailing
|
||||
}
|
||||
|
||||
- (BOOL)hugsBottom
|
||||
{
|
||||
// only hug if we have stretchy
|
||||
return [self nStretchy] != 0;
|
||||
}
|
||||
|
||||
- (int)nStretchy
|
||||
{
|
||||
formChild *fc;
|
||||
int n;
|
||||
|
||||
n = 0;
|
||||
for (fc in self->children) {
|
||||
if (!uiControlVisible(fc.c))
|
||||
continue;
|
||||
if (fc.stretchy)
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static void uiFormDestroy(uiControl *c)
|
||||
{
|
||||
uiForm *f = uiForm(c);
|
||||
|
||||
[f->view onDestroy];
|
||||
[f->view release];
|
||||
uiFreeControl(uiControl(f));
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultHandle(uiForm, view)
|
||||
uiDarwinControlDefaultParent(uiForm, view)
|
||||
uiDarwinControlDefaultSetParent(uiForm, view)
|
||||
uiDarwinControlDefaultToplevel(uiForm, view)
|
||||
uiDarwinControlDefaultVisible(uiForm, view)
|
||||
uiDarwinControlDefaultShow(uiForm, view)
|
||||
uiDarwinControlDefaultHide(uiForm, view)
|
||||
uiDarwinControlDefaultEnabled(uiForm, view)
|
||||
uiDarwinControlDefaultEnable(uiForm, view)
|
||||
uiDarwinControlDefaultDisable(uiForm, view)
|
||||
|
||||
static void uiFormSyncEnableState(uiDarwinControl *c, int enabled)
|
||||
{
|
||||
uiForm *f = uiForm(c);
|
||||
|
||||
if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(f), enabled))
|
||||
return;
|
||||
[f->view syncEnableStates:enabled];
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultSetSuperview(uiForm, view)
|
||||
|
||||
static BOOL uiFormHugsTrailingEdge(uiDarwinControl *c)
|
||||
{
|
||||
uiForm *f = uiForm(c);
|
||||
|
||||
return [f->view hugsTrailing];
|
||||
}
|
||||
|
||||
static BOOL uiFormHugsBottom(uiDarwinControl *c)
|
||||
{
|
||||
uiForm *f = uiForm(c);
|
||||
|
||||
return [f->view hugsBottom];
|
||||
}
|
||||
|
||||
static void uiFormChildEdgeHuggingChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiForm *f = uiForm(c);
|
||||
|
||||
[f->view establishOurConstraints];
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultHuggingPriority(uiForm, view)
|
||||
uiDarwinControlDefaultSetHuggingPriority(uiForm, view)
|
||||
|
||||
static void uiFormChildVisibilityChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiForm *f = uiForm(c);
|
||||
|
||||
[f->view establishOurConstraints];
|
||||
}
|
||||
|
||||
void uiFormAppend(uiForm *f, const char *label, uiControl *c, int stretchy)
|
||||
{
|
||||
// LONGTERM on other platforms
|
||||
// or at leat allow this and implicitly turn it into a spacer
|
||||
if (c == NULL)
|
||||
userbug("You cannot add NULL to a uiForm.");
|
||||
[f->view append:toNSString(label) c:c stretchy:stretchy];
|
||||
}
|
||||
|
||||
void uiFormDelete(uiForm *f, int n)
|
||||
{
|
||||
[f->view delete:n];
|
||||
}
|
||||
|
||||
int uiFormPadded(uiForm *f)
|
||||
{
|
||||
return [f->view isPadded];
|
||||
}
|
||||
|
||||
void uiFormSetPadded(uiForm *f, int padded)
|
||||
{
|
||||
[f->view setPadded:padded];
|
||||
}
|
||||
|
||||
uiForm *uiNewForm(void)
|
||||
{
|
||||
uiForm *f;
|
||||
|
||||
uiDarwinNewControl(uiForm, f);
|
||||
|
||||
f->view = [[formView alloc] initWithF:f];
|
||||
|
||||
return f;
|
||||
}
|
||||
Vendored
+800
@@ -0,0 +1,800 @@
|
||||
// 11 june 2016
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// TODO the assorted test doesn't work right at all
|
||||
|
||||
@interface gridChild : NSView
|
||||
@property uiControl *c;
|
||||
@property int left;
|
||||
@property int top;
|
||||
@property int xspan;
|
||||
@property int yspan;
|
||||
@property int hexpand;
|
||||
@property uiAlign halign;
|
||||
@property int vexpand;
|
||||
@property uiAlign valign;
|
||||
|
||||
@property (strong) NSLayoutConstraint *leadingc;
|
||||
@property (strong) NSLayoutConstraint *topc;
|
||||
@property (strong) NSLayoutConstraint *trailingc;
|
||||
@property (strong) NSLayoutConstraint *bottomc;
|
||||
@property (strong) NSLayoutConstraint *xcenterc;
|
||||
@property (strong) NSLayoutConstraint *ycenterc;
|
||||
|
||||
@property NSLayoutPriority oldHorzHuggingPri;
|
||||
@property NSLayoutPriority oldVertHuggingPri;
|
||||
- (void)setC:(uiControl *)c grid:(uiGrid *)g;
|
||||
- (void)onDestroy;
|
||||
- (NSView *)view;
|
||||
@end
|
||||
|
||||
@interface gridView : NSView {
|
||||
uiGrid *g;
|
||||
NSMutableArray *children;
|
||||
int padded;
|
||||
|
||||
NSMutableArray *edges;
|
||||
NSMutableArray *inBetweens;
|
||||
|
||||
NSMutableArray *emptyCellViews;
|
||||
}
|
||||
- (id)initWithG:(uiGrid *)gg;
|
||||
- (void)onDestroy;
|
||||
- (void)removeOurConstraints;
|
||||
- (void)syncEnableStates:(int)enabled;
|
||||
- (CGFloat)paddingAmount;
|
||||
- (void)establishOurConstraints;
|
||||
- (void)append:(gridChild *)gc;
|
||||
- (void)insert:(gridChild *)gc after:(uiControl *)c at:(uiAt)at;
|
||||
- (int)isPadded;
|
||||
- (void)setPadded:(int)p;
|
||||
- (BOOL)hugsTrailing;
|
||||
- (BOOL)hugsBottom;
|
||||
- (int)nhexpand;
|
||||
- (int)nvexpand;
|
||||
@end
|
||||
|
||||
struct uiGrid {
|
||||
uiDarwinControl c;
|
||||
gridView *view;
|
||||
};
|
||||
|
||||
@implementation gridChild
|
||||
|
||||
- (void)setC:(uiControl *)c grid:(uiGrid *)g
|
||||
{
|
||||
self.c = c;
|
||||
self.oldHorzHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(self.c), NSLayoutConstraintOrientationHorizontal);
|
||||
self.oldVertHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(self.c), NSLayoutConstraintOrientationVertical);
|
||||
|
||||
uiControlSetParent(self.c, uiControl(g));
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(self.c), self);
|
||||
uiDarwinControlSyncEnableState(uiDarwinControl(self.c), uiControlEnabledToUser(uiControl(g)));
|
||||
|
||||
if (self.halign == uiAlignStart || self.halign == uiAlignFill) {
|
||||
self.leadingc = mkConstraint(self, NSLayoutAttributeLeading,
|
||||
NSLayoutRelationEqual,
|
||||
[self view], NSLayoutAttributeLeading,
|
||||
1, 0,
|
||||
@"uiGrid child horizontal alignment start constraint");
|
||||
[self addConstraint:self.leadingc];
|
||||
}
|
||||
if (self.halign == uiAlignCenter) {
|
||||
self.xcenterc = mkConstraint(self, NSLayoutAttributeCenterX,
|
||||
NSLayoutRelationEqual,
|
||||
[self view], NSLayoutAttributeCenterX,
|
||||
1, 0,
|
||||
@"uiGrid child horizontal alignment center constraint");
|
||||
[self addConstraint:self.xcenterc];
|
||||
}
|
||||
if (self.halign == uiAlignEnd || self.halign == uiAlignFill) {
|
||||
self.trailingc = mkConstraint(self, NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationEqual,
|
||||
[self view], NSLayoutAttributeTrailing,
|
||||
1, 0,
|
||||
@"uiGrid child horizontal alignment end constraint");
|
||||
[self addConstraint:self.trailingc];
|
||||
}
|
||||
|
||||
if (self.valign == uiAlignStart || self.valign == uiAlignFill) {
|
||||
self.topc = mkConstraint(self, NSLayoutAttributeTop,
|
||||
NSLayoutRelationEqual,
|
||||
[self view], NSLayoutAttributeTop,
|
||||
1, 0,
|
||||
@"uiGrid child vertical alignment start constraint");
|
||||
[self addConstraint:self.topc];
|
||||
}
|
||||
if (self.valign == uiAlignCenter) {
|
||||
self.ycenterc = mkConstraint(self, NSLayoutAttributeCenterY,
|
||||
NSLayoutRelationEqual,
|
||||
[self view], NSLayoutAttributeCenterY,
|
||||
1, 0,
|
||||
@"uiGrid child vertical alignment center constraint");
|
||||
[self addConstraint:self.ycenterc];
|
||||
}
|
||||
if (self.valign == uiAlignEnd || self.valign == uiAlignFill) {
|
||||
self.bottomc = mkConstraint(self, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationEqual,
|
||||
[self view], NSLayoutAttributeBottom,
|
||||
1, 0,
|
||||
@"uiGrid child vertical alignment end constraint");
|
||||
[self addConstraint:self.bottomc];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onDestroy
|
||||
{
|
||||
if (self.leadingc != nil) {
|
||||
[self removeConstraint:self.leadingc];
|
||||
self.leadingc = nil;
|
||||
}
|
||||
if (self.topc != nil) {
|
||||
[self removeConstraint:self.topc];
|
||||
self.topc = nil;
|
||||
}
|
||||
if (self.trailingc != nil) {
|
||||
[self removeConstraint:self.trailingc];
|
||||
self.trailingc = nil;
|
||||
}
|
||||
if (self.bottomc != nil) {
|
||||
[self removeConstraint:self.bottomc];
|
||||
self.bottomc = nil;
|
||||
}
|
||||
if (self.xcenterc != nil) {
|
||||
[self removeConstraint:self.xcenterc];
|
||||
self.xcenterc = nil;
|
||||
}
|
||||
if (self.ycenterc != nil) {
|
||||
[self removeConstraint:self.ycenterc];
|
||||
self.ycenterc = nil;
|
||||
}
|
||||
|
||||
uiControlSetParent(self.c, NULL);
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(self.c), nil);
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(self.c), self.oldHorzHuggingPri, NSLayoutConstraintOrientationHorizontal);
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(self.c), self.oldVertHuggingPri, NSLayoutConstraintOrientationVertical);
|
||||
}
|
||||
|
||||
- (NSView *)view
|
||||
{
|
||||
return (NSView *) uiControlHandle(self.c);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation gridView
|
||||
|
||||
- (id)initWithG:(uiGrid *)gg
|
||||
{
|
||||
self = [super initWithFrame:NSZeroRect];
|
||||
if (self != nil) {
|
||||
self->g = gg;
|
||||
self->padded = 0;
|
||||
self->children = [NSMutableArray new];
|
||||
|
||||
self->edges = [NSMutableArray new];
|
||||
self->inBetweens = [NSMutableArray new];
|
||||
|
||||
self->emptyCellViews = [NSMutableArray new];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)onDestroy
|
||||
{
|
||||
gridChild *gc;
|
||||
|
||||
[self removeOurConstraints];
|
||||
[self->edges release];
|
||||
[self->inBetweens release];
|
||||
|
||||
[self->emptyCellViews release];
|
||||
|
||||
for (gc in self->children) {
|
||||
[gc onDestroy];
|
||||
uiControlDestroy(gc.c);
|
||||
[gc removeFromSuperview];
|
||||
}
|
||||
[self->children release];
|
||||
}
|
||||
|
||||
- (void)removeOurConstraints
|
||||
{
|
||||
NSView *v;
|
||||
|
||||
if ([self->edges count] != 0) {
|
||||
[self removeConstraints:self->edges];
|
||||
[self->edges removeAllObjects];
|
||||
}
|
||||
if ([self->inBetweens count] != 0) {
|
||||
[self removeConstraints:self->inBetweens];
|
||||
[self->inBetweens removeAllObjects];
|
||||
}
|
||||
|
||||
for (v in self->emptyCellViews)
|
||||
[v removeFromSuperview];
|
||||
[self->emptyCellViews removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)syncEnableStates:(int)enabled
|
||||
{
|
||||
gridChild *gc;
|
||||
|
||||
for (gc in self->children)
|
||||
uiDarwinControlSyncEnableState(uiDarwinControl(gc.c), enabled);
|
||||
}
|
||||
|
||||
- (CGFloat)paddingAmount
|
||||
{
|
||||
if (!self->padded)
|
||||
return 0.0;
|
||||
return uiDarwinPaddingAmount(NULL);
|
||||
}
|
||||
|
||||
// LONGTERM stop early if all controls are hidden
|
||||
- (void)establishOurConstraints
|
||||
{
|
||||
gridChild *gc;
|
||||
CGFloat padding;
|
||||
int xmin, ymin;
|
||||
int xmax, ymax;
|
||||
int xcount, ycount;
|
||||
BOOL first;
|
||||
int **gg;
|
||||
NSView ***gv;
|
||||
BOOL **gspan;
|
||||
int x, y;
|
||||
int i;
|
||||
NSLayoutConstraint *c;
|
||||
int firstx, firsty;
|
||||
BOOL *hexpand, *vexpand;
|
||||
BOOL doit;
|
||||
BOOL onlyEmptyAndSpanning;
|
||||
|
||||
[self removeOurConstraints];
|
||||
if ([self->children count] == 0)
|
||||
return;
|
||||
padding = [self paddingAmount];
|
||||
|
||||
// first, figure out the minimum and maximum row and column numbers
|
||||
// ignore hidden controls
|
||||
first = YES;
|
||||
for (gc in self->children) {
|
||||
// this bit is important: it ensures row ymin and column xmin have at least one cell to draw, so the onlyEmptyAndSpanning logic below will never run on those rows
|
||||
if (!uiControlVisible(gc.c))
|
||||
continue;
|
||||
if (first) {
|
||||
xmin = gc.left;
|
||||
ymin = gc.top;
|
||||
xmax = gc.left + gc.xspan;
|
||||
ymax = gc.top + gc.yspan;
|
||||
first = NO;
|
||||
continue;
|
||||
}
|
||||
if (xmin > gc.left)
|
||||
xmin = gc.left;
|
||||
if (ymin > gc.top)
|
||||
ymin = gc.top;
|
||||
if (xmax < (gc.left + gc.xspan))
|
||||
xmax = gc.left + gc.xspan;
|
||||
if (ymax < (gc.top + gc.yspan))
|
||||
ymax = gc.top + gc.yspan;
|
||||
}
|
||||
if (first != NO) // the entire grid is hidden; do nothing
|
||||
return;
|
||||
xcount = xmax - xmin;
|
||||
ycount = ymax - ymin;
|
||||
|
||||
// now build a topological map of the grid gg[y][x]
|
||||
// also figure out which cells contain spanned views so they can be ignored later
|
||||
// treat hidden controls by keeping the indices -1
|
||||
gg = (int **) uiAlloc(ycount * sizeof (int *), "int[][]");
|
||||
gspan = (BOOL **) uiAlloc(ycount * sizeof (BOOL *), "BOOL[][]");
|
||||
for (y = 0; y < ycount; y++) {
|
||||
gg[y] = (int *) uiAlloc(xcount * sizeof (int), "int[]");
|
||||
gspan[y] = (BOOL *) uiAlloc(xcount * sizeof (BOOL), "BOOL[]");
|
||||
for (x = 0; x < xcount; x++)
|
||||
gg[y][x] = -1; // empty
|
||||
}
|
||||
for (i = 0; i < [self->children count]; i++) {
|
||||
gc = (gridChild *) [self->children objectAtIndex:i];
|
||||
if (!uiControlVisible(gc.c))
|
||||
continue;
|
||||
for (y = gc.top; y < gc.top + gc.yspan; y++)
|
||||
for (x = gc.left; x < gc.left + gc.xspan; x++) {
|
||||
gg[y - ymin][x - xmin] = i;
|
||||
if (x != gc.left || y != gc.top)
|
||||
gspan[y - ymin][x - xmin] = YES;
|
||||
}
|
||||
}
|
||||
|
||||
// if a row or column only contains emptys and spanning cells of a opposite-direction spannings, remove it by duplicating the previous row or column
|
||||
for (y = 0; y < ycount; y++) {
|
||||
onlyEmptyAndSpanning = YES;
|
||||
for (x = 0; x < xcount; x++)
|
||||
if (gg[y][x] != -1) {
|
||||
gc = (gridChild *) [self->children objectAtIndex:gg[y][x]];
|
||||
if (gc.yspan == 1 || gc.top - ymin == y) {
|
||||
onlyEmptyAndSpanning = NO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (onlyEmptyAndSpanning)
|
||||
for (x = 0; x < xcount; x++) {
|
||||
gg[y][x] = gg[y - 1][x];
|
||||
gspan[y][x] = YES;
|
||||
}
|
||||
}
|
||||
for (x = 0; x < xcount; x++) {
|
||||
onlyEmptyAndSpanning = YES;
|
||||
for (y = 0; y < ycount; y++)
|
||||
if (gg[y][x] != -1) {
|
||||
gc = (gridChild *) [self->children objectAtIndex:gg[y][x]];
|
||||
if (gc.xspan == 1 || gc.left - xmin == x) {
|
||||
onlyEmptyAndSpanning = NO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (onlyEmptyAndSpanning)
|
||||
for (y = 0; y < ycount; y++) {
|
||||
gg[y][x] = gg[y][x - 1];
|
||||
gspan[y][x] = YES;
|
||||
}
|
||||
}
|
||||
|
||||
// now build a topological map of the grid's views gv[y][x]
|
||||
// for any empty cell, create a dummy view
|
||||
gv = (NSView ***) uiAlloc(ycount * sizeof (NSView **), "NSView *[][]");
|
||||
for (y = 0; y < ycount; y++) {
|
||||
gv[y] = (NSView **) uiAlloc(xcount * sizeof (NSView *), "NSView *[]");
|
||||
for (x = 0; x < xcount; x++)
|
||||
if (gg[y][x] == -1) {
|
||||
gv[y][x] = [[NSView alloc] initWithFrame:NSZeroRect];
|
||||
[gv[y][x] setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self addSubview:gv[y][x]];
|
||||
[self->emptyCellViews addObject:gv[y][x]];
|
||||
} else {
|
||||
gc = (gridChild *) [self->children objectAtIndex:gg[y][x]];
|
||||
gv[y][x] = gc;
|
||||
}
|
||||
}
|
||||
|
||||
// now figure out which rows and columns really expand
|
||||
hexpand = (BOOL *) uiAlloc(xcount * sizeof (BOOL), "BOOL[]");
|
||||
vexpand = (BOOL *) uiAlloc(ycount * sizeof (BOOL), "BOOL[]");
|
||||
// first, which don't span
|
||||
for (gc in self->children) {
|
||||
if (!uiControlVisible(gc.c))
|
||||
continue;
|
||||
if (gc.hexpand && gc.xspan == 1)
|
||||
hexpand[gc.left - xmin] = YES;
|
||||
if (gc.vexpand && gc.yspan == 1)
|
||||
vexpand[gc.top - ymin] = YES;
|
||||
}
|
||||
// second, which do span
|
||||
// the way we handle this is simple: if none of the spanned rows/columns expand, make all rows/columns expand
|
||||
for (gc in self->children) {
|
||||
if (!uiControlVisible(gc.c))
|
||||
continue;
|
||||
if (gc.hexpand && gc.xspan != 1) {
|
||||
doit = YES;
|
||||
for (x = gc.left; x < gc.left + gc.xspan; x++)
|
||||
if (hexpand[x - xmin]) {
|
||||
doit = NO;
|
||||
break;
|
||||
}
|
||||
if (doit)
|
||||
for (x = gc.left; x < gc.left + gc.xspan; x++)
|
||||
hexpand[x - xmin] = YES;
|
||||
}
|
||||
if (gc.vexpand && gc.yspan != 1) {
|
||||
doit = YES;
|
||||
for (y = gc.top; y < gc.top + gc.yspan; y++)
|
||||
if (vexpand[y - ymin]) {
|
||||
doit = NO;
|
||||
break;
|
||||
}
|
||||
if (doit)
|
||||
for (y = gc.top; y < gc.top + gc.yspan; y++)
|
||||
vexpand[y - ymin] = YES;
|
||||
}
|
||||
}
|
||||
|
||||
// now establish all the edge constraints
|
||||
// leading and trailing edges
|
||||
for (y = 0; y < ycount; y++) {
|
||||
c = mkConstraint(self, NSLayoutAttributeLeading,
|
||||
NSLayoutRelationEqual,
|
||||
gv[y][0], NSLayoutAttributeLeading,
|
||||
1, 0,
|
||||
@"uiGrid leading edge constraint");
|
||||
[self addConstraint:c];
|
||||
[self->edges addObject:c];
|
||||
c = mkConstraint(self, NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationEqual,
|
||||
gv[y][xcount - 1], NSLayoutAttributeTrailing,
|
||||
1, 0,
|
||||
@"uiGrid trailing edge constraint");
|
||||
[self addConstraint:c];
|
||||
[self->edges addObject:c];
|
||||
}
|
||||
// top and bottom edges
|
||||
for (x = 0; x < xcount; x++) {
|
||||
c = mkConstraint(self, NSLayoutAttributeTop,
|
||||
NSLayoutRelationEqual,
|
||||
gv[0][x], NSLayoutAttributeTop,
|
||||
1, 0,
|
||||
@"uiGrid top edge constraint");
|
||||
[self addConstraint:c];
|
||||
[self->edges addObject:c];
|
||||
c = mkConstraint(self, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationEqual,
|
||||
gv[ycount - 1][x], NSLayoutAttributeBottom,
|
||||
1, 0,
|
||||
@"uiGrid bottom edge constraint");
|
||||
[self addConstraint:c];
|
||||
[self->edges addObject:c];
|
||||
}
|
||||
|
||||
// now align leading and top edges
|
||||
// do NOT align spanning cells!
|
||||
for (x = 0; x < xcount; x++) {
|
||||
for (y = 0; y < ycount; y++)
|
||||
if (!gspan[y][x])
|
||||
break;
|
||||
firsty = y;
|
||||
for (y++; y < ycount; y++) {
|
||||
if (gspan[y][x])
|
||||
continue;
|
||||
c = mkConstraint(gv[firsty][x], NSLayoutAttributeLeading,
|
||||
NSLayoutRelationEqual,
|
||||
gv[y][x], NSLayoutAttributeLeading,
|
||||
1, 0,
|
||||
@"uiGrid column leading constraint");
|
||||
[self addConstraint:c];
|
||||
[self->edges addObject:c];
|
||||
}
|
||||
}
|
||||
for (y = 0; y < ycount; y++) {
|
||||
for (x = 0; x < xcount; x++)
|
||||
if (!gspan[y][x])
|
||||
break;
|
||||
firstx = x;
|
||||
for (x++; x < xcount; x++) {
|
||||
if (gspan[y][x])
|
||||
continue;
|
||||
c = mkConstraint(gv[y][firstx], NSLayoutAttributeTop,
|
||||
NSLayoutRelationEqual,
|
||||
gv[y][x], NSLayoutAttributeTop,
|
||||
1, 0,
|
||||
@"uiGrid row top constraint");
|
||||
[self addConstraint:c];
|
||||
[self->edges addObject:c];
|
||||
}
|
||||
}
|
||||
|
||||
// now string adjacent views together
|
||||
for (y = 0; y < ycount; y++)
|
||||
for (x = 1; x < xcount; x++)
|
||||
if (gv[y][x - 1] != gv[y][x]) {
|
||||
c = mkConstraint(gv[y][x - 1], NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationEqual,
|
||||
gv[y][x], NSLayoutAttributeLeading,
|
||||
1, -padding,
|
||||
@"uiGrid internal horizontal constraint");
|
||||
[self addConstraint:c];
|
||||
[self->inBetweens addObject:c];
|
||||
}
|
||||
for (x = 0; x < xcount; x++)
|
||||
for (y = 1; y < ycount; y++)
|
||||
if (gv[y - 1][x] != gv[y][x]) {
|
||||
c = mkConstraint(gv[y - 1][x], NSLayoutAttributeBottom,
|
||||
NSLayoutRelationEqual,
|
||||
gv[y][x], NSLayoutAttributeTop,
|
||||
1, -padding,
|
||||
@"uiGrid internal vertical constraint");
|
||||
[self addConstraint:c];
|
||||
[self->inBetweens addObject:c];
|
||||
}
|
||||
|
||||
// now set priorities for all widgets that expand or not
|
||||
// if a cell is in an expanding row, OR If it spans, then it must be willing to stretch
|
||||
// otherwise, it tries not to
|
||||
// note we don't use NSLayoutPriorityRequired as that will cause things to squish when they shouldn't
|
||||
for (gc in self->children) {
|
||||
NSLayoutPriority priority;
|
||||
|
||||
if (!uiControlVisible(gc.c))
|
||||
continue;
|
||||
if (hexpand[gc.left - xmin] || gc.xspan != 1)
|
||||
priority = NSLayoutPriorityDefaultLow;
|
||||
else
|
||||
priority = NSLayoutPriorityDefaultHigh;
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(gc.c), priority, NSLayoutConstraintOrientationHorizontal);
|
||||
// same for vertical direction
|
||||
if (vexpand[gc.top - ymin] || gc.yspan != 1)
|
||||
priority = NSLayoutPriorityDefaultLow;
|
||||
else
|
||||
priority = NSLayoutPriorityDefaultHigh;
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(gc.c), priority, NSLayoutConstraintOrientationVertical);
|
||||
}
|
||||
|
||||
// TODO make all expanding rows/columns the same height/width
|
||||
|
||||
// and finally clean up
|
||||
uiFree(hexpand);
|
||||
uiFree(vexpand);
|
||||
for (y = 0; y < ycount; y++) {
|
||||
uiFree(gg[y]);
|
||||
uiFree(gv[y]);
|
||||
uiFree(gspan[y]);
|
||||
}
|
||||
uiFree(gg);
|
||||
uiFree(gv);
|
||||
uiFree(gspan);
|
||||
}
|
||||
|
||||
- (void)append:(gridChild *)gc
|
||||
{
|
||||
BOOL update;
|
||||
int oldnh, oldnv;
|
||||
|
||||
[gc setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self addSubview:gc];
|
||||
|
||||
// no need to set priority here; that's done in establishOurConstraints
|
||||
|
||||
oldnh = [self nhexpand];
|
||||
oldnv = [self nvexpand];
|
||||
[self->children addObject:gc];
|
||||
|
||||
[self establishOurConstraints];
|
||||
update = NO;
|
||||
if (gc.hexpand)
|
||||
if (oldnh == 0)
|
||||
update = YES;
|
||||
if (gc.vexpand)
|
||||
if (oldnv == 0)
|
||||
update = YES;
|
||||
if (update)
|
||||
uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->g));
|
||||
|
||||
[gc release]; // we don't need the initial reference now
|
||||
}
|
||||
|
||||
- (void)insert:(gridChild *)gc after:(uiControl *)c at:(uiAt)at
|
||||
{
|
||||
gridChild *other;
|
||||
BOOL found;
|
||||
|
||||
found = NO;
|
||||
for (other in self->children)
|
||||
if (other.c == c) {
|
||||
found = YES;
|
||||
break;
|
||||
}
|
||||
if (!found)
|
||||
userbug("Existing control %p is not in grid %p; you cannot add other controls next to it", c, self->g);
|
||||
|
||||
switch (at) {
|
||||
case uiAtLeading:
|
||||
gc.left = other.left - gc.xspan;
|
||||
gc.top = other.top;
|
||||
break;
|
||||
case uiAtTop:
|
||||
gc.left = other.left;
|
||||
gc.top = other.top - gc.yspan;
|
||||
break;
|
||||
case uiAtTrailing:
|
||||
gc.left = other.left + other.xspan;
|
||||
gc.top = other.top;
|
||||
break;
|
||||
case uiAtBottom:
|
||||
gc.left = other.left;
|
||||
gc.top = other.top + other.yspan;
|
||||
break;
|
||||
// TODO add error checks to ALL enums
|
||||
}
|
||||
|
||||
[self append:gc];
|
||||
}
|
||||
|
||||
- (int)isPadded
|
||||
{
|
||||
return self->padded;
|
||||
}
|
||||
|
||||
- (void)setPadded:(int)p
|
||||
{
|
||||
CGFloat padding;
|
||||
NSLayoutConstraint *c;
|
||||
|
||||
#if 0 /* TODO */
|
||||
dispatch_after(
|
||||
dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC),
|
||||
dispatch_get_main_queue(),
|
||||
^{ [[self window] visualizeConstraints:[self constraints]]; }
|
||||
);
|
||||
#endif
|
||||
self->padded = p;
|
||||
padding = [self paddingAmount];
|
||||
for (c in self->inBetweens)
|
||||
switch ([c firstAttribute]) {
|
||||
case NSLayoutAttributeLeading:
|
||||
case NSLayoutAttributeTop:
|
||||
[c setConstant:padding];
|
||||
break;
|
||||
case NSLayoutAttributeTrailing:
|
||||
case NSLayoutAttributeBottom:
|
||||
[c setConstant:-padding];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)hugsTrailing
|
||||
{
|
||||
// only hug if we have horizontally expanding
|
||||
return [self nhexpand] != 0;
|
||||
}
|
||||
|
||||
- (BOOL)hugsBottom
|
||||
{
|
||||
// only hug if we have vertically expanding
|
||||
return [self nvexpand] != 0;
|
||||
}
|
||||
|
||||
- (int)nhexpand
|
||||
{
|
||||
gridChild *gc;
|
||||
int n;
|
||||
|
||||
n = 0;
|
||||
for (gc in self->children) {
|
||||
if (!uiControlVisible(gc.c))
|
||||
continue;
|
||||
if (gc.hexpand)
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
- (int)nvexpand
|
||||
{
|
||||
gridChild *gc;
|
||||
int n;
|
||||
|
||||
n = 0;
|
||||
for (gc in self->children) {
|
||||
if (!uiControlVisible(gc.c))
|
||||
continue;
|
||||
if (gc.vexpand)
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static void uiGridDestroy(uiControl *c)
|
||||
{
|
||||
uiGrid *g = uiGrid(c);
|
||||
|
||||
[g->view onDestroy];
|
||||
[g->view release];
|
||||
uiFreeControl(uiControl(g));
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultHandle(uiGrid, view)
|
||||
uiDarwinControlDefaultParent(uiGrid, view)
|
||||
uiDarwinControlDefaultSetParent(uiGrid, view)
|
||||
uiDarwinControlDefaultToplevel(uiGrid, view)
|
||||
uiDarwinControlDefaultVisible(uiGrid, view)
|
||||
uiDarwinControlDefaultShow(uiGrid, view)
|
||||
uiDarwinControlDefaultHide(uiGrid, view)
|
||||
uiDarwinControlDefaultEnabled(uiGrid, view)
|
||||
uiDarwinControlDefaultEnable(uiGrid, view)
|
||||
uiDarwinControlDefaultDisable(uiGrid, view)
|
||||
|
||||
static void uiGridSyncEnableState(uiDarwinControl *c, int enabled)
|
||||
{
|
||||
uiGrid *g = uiGrid(c);
|
||||
|
||||
if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(g), enabled))
|
||||
return;
|
||||
[g->view syncEnableStates:enabled];
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultSetSuperview(uiGrid, view)
|
||||
|
||||
static BOOL uiGridHugsTrailingEdge(uiDarwinControl *c)
|
||||
{
|
||||
uiGrid *g = uiGrid(c);
|
||||
|
||||
return [g->view hugsTrailing];
|
||||
}
|
||||
|
||||
static BOOL uiGridHugsBottom(uiDarwinControl *c)
|
||||
{
|
||||
uiGrid *g = uiGrid(c);
|
||||
|
||||
return [g->view hugsBottom];
|
||||
}
|
||||
|
||||
static void uiGridChildEdgeHuggingChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiGrid *g = uiGrid(c);
|
||||
|
||||
[g->view establishOurConstraints];
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultHuggingPriority(uiGrid, view)
|
||||
uiDarwinControlDefaultSetHuggingPriority(uiGrid, view)
|
||||
|
||||
static void uiGridChildVisibilityChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiGrid *g = uiGrid(c);
|
||||
|
||||
[g->view establishOurConstraints];
|
||||
}
|
||||
|
||||
static gridChild *toChild(uiControl *c, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign, uiGrid *g)
|
||||
{
|
||||
gridChild *gc;
|
||||
|
||||
if (xspan < 0)
|
||||
userbug("You cannot have a negative xspan in a uiGrid cell.");
|
||||
if (yspan < 0)
|
||||
userbug("You cannot have a negative yspan in a uiGrid cell.");
|
||||
gc = [gridChild new];
|
||||
gc.xspan = xspan;
|
||||
gc.yspan = yspan;
|
||||
gc.hexpand = hexpand;
|
||||
gc.halign = halign;
|
||||
gc.vexpand = vexpand;
|
||||
gc.valign = valign;
|
||||
[gc setC:c grid:g];
|
||||
return gc;
|
||||
}
|
||||
|
||||
void uiGridAppend(uiGrid *g, uiControl *c, int left, int top, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign)
|
||||
{
|
||||
gridChild *gc;
|
||||
|
||||
// LONGTERM on other platforms
|
||||
// or at leat allow this and implicitly turn it into a spacer
|
||||
if (c == NULL)
|
||||
userbug("You cannot add NULL to a uiGrid.");
|
||||
gc = toChild(c, xspan, yspan, hexpand, halign, vexpand, valign, g);
|
||||
gc.left = left;
|
||||
gc.top = top;
|
||||
[g->view append:gc];
|
||||
}
|
||||
|
||||
void uiGridInsertAt(uiGrid *g, uiControl *c, uiControl *existing, uiAt at, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign)
|
||||
{
|
||||
gridChild *gc;
|
||||
|
||||
gc = toChild(c, xspan, yspan, hexpand, halign, vexpand, valign, g);
|
||||
[g->view insert:gc after:existing at:at];
|
||||
}
|
||||
|
||||
int uiGridPadded(uiGrid *g)
|
||||
{
|
||||
return [g->view isPadded];
|
||||
}
|
||||
|
||||
void uiGridSetPadded(uiGrid *g, int padded)
|
||||
{
|
||||
[g->view setPadded:padded];
|
||||
}
|
||||
|
||||
uiGrid *uiNewGrid(void)
|
||||
{
|
||||
uiGrid *g;
|
||||
|
||||
uiDarwinNewControl(uiGrid, g);
|
||||
|
||||
g->view = [[gridView alloc] initWithG:g];
|
||||
|
||||
return g;
|
||||
}
|
||||
Vendored
+194
@@ -0,0 +1,194 @@
|
||||
// 14 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
struct uiGroup {
|
||||
uiDarwinControl c;
|
||||
NSBox *box;
|
||||
uiControl *child;
|
||||
NSLayoutPriority oldHorzHuggingPri;
|
||||
NSLayoutPriority oldVertHuggingPri;
|
||||
int margined;
|
||||
struct singleChildConstraints constraints;
|
||||
NSLayoutPriority horzHuggingPri;
|
||||
NSLayoutPriority vertHuggingPri;
|
||||
};
|
||||
|
||||
static void removeConstraints(uiGroup *g)
|
||||
{
|
||||
// set to contentView instead of to the box itself, otherwise we get clipping underneath the label
|
||||
singleChildConstraintsRemove(&(g->constraints), [g->box contentView]);
|
||||
}
|
||||
|
||||
static void uiGroupDestroy(uiControl *c)
|
||||
{
|
||||
uiGroup *g = uiGroup(c);
|
||||
|
||||
removeConstraints(g);
|
||||
if (g->child != NULL) {
|
||||
uiControlSetParent(g->child, NULL);
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(g->child), nil);
|
||||
uiControlDestroy(g->child);
|
||||
}
|
||||
[g->box release];
|
||||
uiFreeControl(uiControl(g));
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultHandle(uiGroup, box)
|
||||
uiDarwinControlDefaultParent(uiGroup, box)
|
||||
uiDarwinControlDefaultSetParent(uiGroup, box)
|
||||
uiDarwinControlDefaultToplevel(uiGroup, box)
|
||||
uiDarwinControlDefaultVisible(uiGroup, box)
|
||||
uiDarwinControlDefaultShow(uiGroup, box)
|
||||
uiDarwinControlDefaultHide(uiGroup, box)
|
||||
uiDarwinControlDefaultEnabled(uiGroup, box)
|
||||
uiDarwinControlDefaultEnable(uiGroup, box)
|
||||
uiDarwinControlDefaultDisable(uiGroup, box)
|
||||
|
||||
static void uiGroupSyncEnableState(uiDarwinControl *c, int enabled)
|
||||
{
|
||||
uiGroup *g = uiGroup(c);
|
||||
|
||||
if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(g), enabled))
|
||||
return;
|
||||
if (g->child != NULL)
|
||||
uiDarwinControlSyncEnableState(uiDarwinControl(g->child), enabled);
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultSetSuperview(uiGroup, box)
|
||||
|
||||
static void groupRelayout(uiGroup *g)
|
||||
{
|
||||
NSView *childView;
|
||||
|
||||
removeConstraints(g);
|
||||
if (g->child == NULL)
|
||||
return;
|
||||
childView = (NSView *) uiControlHandle(g->child);
|
||||
singleChildConstraintsEstablish(&(g->constraints),
|
||||
[g->box contentView], childView,
|
||||
uiDarwinControlHugsTrailingEdge(uiDarwinControl(g->child)),
|
||||
uiDarwinControlHugsBottom(uiDarwinControl(g->child)),
|
||||
g->margined,
|
||||
@"uiGroup");
|
||||
// needed for some very rare drawing errors...
|
||||
jiggleViewLayout(g->box);
|
||||
}
|
||||
|
||||
// TODO rename these since I'm starting to get confused by what they mean by hugging
|
||||
BOOL uiGroupHugsTrailingEdge(uiDarwinControl *c)
|
||||
{
|
||||
uiGroup *g = uiGroup(c);
|
||||
|
||||
// TODO make a function?
|
||||
return g->horzHuggingPri < NSLayoutPriorityWindowSizeStayPut;
|
||||
}
|
||||
|
||||
BOOL uiGroupHugsBottom(uiDarwinControl *c)
|
||||
{
|
||||
uiGroup *g = uiGroup(c);
|
||||
|
||||
return g->vertHuggingPri < NSLayoutPriorityWindowSizeStayPut;
|
||||
}
|
||||
|
||||
static void uiGroupChildEdgeHuggingChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiGroup *g = uiGroup(c);
|
||||
|
||||
groupRelayout(g);
|
||||
}
|
||||
|
||||
static NSLayoutPriority uiGroupHuggingPriority(uiDarwinControl *c, NSLayoutConstraintOrientation orientation)
|
||||
{
|
||||
uiGroup *g = uiGroup(c);
|
||||
|
||||
if (orientation == NSLayoutConstraintOrientationHorizontal)
|
||||
return g->horzHuggingPri;
|
||||
return g->vertHuggingPri;
|
||||
}
|
||||
|
||||
static void uiGroupSetHuggingPriority(uiDarwinControl *c, NSLayoutPriority priority, NSLayoutConstraintOrientation orientation)
|
||||
{
|
||||
uiGroup *g = uiGroup(c);
|
||||
|
||||
if (orientation == NSLayoutConstraintOrientationHorizontal)
|
||||
g->horzHuggingPri = priority;
|
||||
else
|
||||
g->vertHuggingPri = priority;
|
||||
uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(g));
|
||||
}
|
||||
|
||||
static void uiGroupChildVisibilityChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiGroup *g = uiGroup(c);
|
||||
|
||||
groupRelayout(g);
|
||||
}
|
||||
|
||||
char *uiGroupTitle(uiGroup *g)
|
||||
{
|
||||
return uiDarwinNSStringToText([g->box title]);
|
||||
}
|
||||
|
||||
void uiGroupSetTitle(uiGroup *g, const char *title)
|
||||
{
|
||||
[g->box setTitle:toNSString(title)];
|
||||
}
|
||||
|
||||
void uiGroupSetChild(uiGroup *g, uiControl *child)
|
||||
{
|
||||
NSView *childView;
|
||||
|
||||
if (g->child != NULL) {
|
||||
removeConstraints(g);
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(g->child), g->oldHorzHuggingPri, NSLayoutConstraintOrientationHorizontal);
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(g->child), g->oldVertHuggingPri, NSLayoutConstraintOrientationVertical);
|
||||
uiControlSetParent(g->child, NULL);
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(g->child), nil);
|
||||
}
|
||||
g->child = child;
|
||||
if (g->child != NULL) {
|
||||
childView = (NSView *) uiControlHandle(g->child);
|
||||
uiControlSetParent(g->child, uiControl(g));
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(g->child), [g->box contentView]);
|
||||
uiDarwinControlSyncEnableState(uiDarwinControl(g->child), uiControlEnabledToUser(uiControl(g)));
|
||||
// don't hug, just in case we're a stretchy group
|
||||
g->oldHorzHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(g->child), NSLayoutConstraintOrientationHorizontal);
|
||||
g->oldVertHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(g->child), NSLayoutConstraintOrientationVertical);
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(g->child), NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationHorizontal);
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(g->child), NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationVertical);
|
||||
}
|
||||
groupRelayout(g);
|
||||
}
|
||||
|
||||
int uiGroupMargined(uiGroup *g)
|
||||
{
|
||||
return g->margined;
|
||||
}
|
||||
|
||||
void uiGroupSetMargined(uiGroup *g, int margined)
|
||||
{
|
||||
g->margined = margined;
|
||||
singleChildConstraintsSetMargined(&(g->constraints), g->margined);
|
||||
}
|
||||
|
||||
uiGroup *uiNewGroup(const char *title)
|
||||
{
|
||||
uiGroup *g;
|
||||
|
||||
uiDarwinNewControl(uiGroup, g);
|
||||
|
||||
g->box = [[NSBox alloc] initWithFrame:NSZeroRect];
|
||||
[g->box setTitle:toNSString(title)];
|
||||
[g->box setBoxType:NSBoxPrimary];
|
||||
[g->box setBorderType:NSLineBorder];
|
||||
[g->box setTransparent:NO];
|
||||
[g->box setTitlePosition:NSAtTop];
|
||||
// we can't use uiDarwinSetControlFont() because the selector is different
|
||||
[g->box setTitleFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
|
||||
|
||||
// default to low hugging to not hug edges
|
||||
g->horzHuggingPri = NSLayoutPriorityDefaultLow;
|
||||
g->vertHuggingPri = NSLayoutPriorityDefaultLow;
|
||||
|
||||
return g;
|
||||
}
|
||||
Vendored
+82
@@ -0,0 +1,82 @@
|
||||
// 25 june 2016
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
struct uiImage {
|
||||
NSImage *i;
|
||||
NSSize size;
|
||||
NSMutableArray *swizzled;
|
||||
};
|
||||
|
||||
uiImage *uiNewImage(double width, double height)
|
||||
{
|
||||
uiImage *i;
|
||||
|
||||
i = uiNew(uiImage);
|
||||
i->size = NSMakeSize(width, height);
|
||||
i->i = [[NSImage alloc] initWithSize:i->size];
|
||||
i->swizzled = [NSMutableArray new];
|
||||
return i;
|
||||
}
|
||||
|
||||
void uiFreeImage(uiImage *i)
|
||||
{
|
||||
NSValue *v;
|
||||
|
||||
[i->i release];
|
||||
// to be safe, do this after releasing the image
|
||||
for (v in i->swizzled)
|
||||
uiFree([v pointerValue]);
|
||||
[i->swizzled release];
|
||||
uiFree(i);
|
||||
}
|
||||
|
||||
void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int pixelStride)
|
||||
{
|
||||
NSBitmapImageRep *repCalibrated, *repsRGB;
|
||||
uint8_t *swizzled, *bp, *sp;
|
||||
int x, y;
|
||||
unsigned char *pix[1];
|
||||
|
||||
// OS X demands that R and B are in the opposite order from what we expect
|
||||
// we must swizzle :(
|
||||
// LONGTERM test on a big-endian system
|
||||
swizzled = (uint8_t *) uiAlloc((pixelStride * pixelHeight * 4) * sizeof (uint8_t), "uint8_t[]");
|
||||
bp = (uint8_t *) pixels;
|
||||
sp = swizzled;
|
||||
for (y = 0; y < pixelHeight * pixelStride; y += pixelStride)
|
||||
for (x = 0; x < pixelStride; x++) {
|
||||
sp[0] = bp[2];
|
||||
sp[1] = bp[1];
|
||||
sp[2] = bp[0];
|
||||
sp[3] = bp[3];
|
||||
sp += 4;
|
||||
bp += 4;
|
||||
}
|
||||
|
||||
pix[0] = (unsigned char *) swizzled;
|
||||
repCalibrated = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:pix
|
||||
pixelsWide:pixelWidth
|
||||
pixelsHigh:pixelHeight
|
||||
bitsPerSample:8
|
||||
samplesPerPixel:4
|
||||
hasAlpha:YES
|
||||
isPlanar:NO
|
||||
colorSpaceName:NSCalibratedRGBColorSpace
|
||||
bitmapFormat:0
|
||||
bytesPerRow:pixelStride
|
||||
bitsPerPixel:32];
|
||||
repsRGB = [repCalibrated bitmapImageRepByRetaggingWithColorSpace:[NSColorSpace sRGBColorSpace]];
|
||||
[repCalibrated release];
|
||||
|
||||
[i->i addRepresentation:repsRGB];
|
||||
[repsRGB setSize:i->size];
|
||||
[repsRGB release];
|
||||
|
||||
// we need to keep swizzled alive for NSBitmapImageRep
|
||||
[i->swizzled addObject:[NSValue valueWithPointer:swizzled]];
|
||||
}
|
||||
|
||||
NSImage *imageImage(uiImage *i)
|
||||
{
|
||||
return i->i;
|
||||
}
|
||||
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
// 14 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
struct uiLabel {
|
||||
uiDarwinControl c;
|
||||
NSTextField *textfield;
|
||||
};
|
||||
|
||||
uiDarwinControlAllDefaults(uiLabel, textfield)
|
||||
|
||||
char *uiLabelText(uiLabel *l)
|
||||
{
|
||||
return uiDarwinNSStringToText([l->textfield stringValue]);
|
||||
}
|
||||
|
||||
void uiLabelSetText(uiLabel *l, const char *text)
|
||||
{
|
||||
[l->textfield setStringValue:toNSString(text)];
|
||||
}
|
||||
|
||||
NSTextField *newLabel(NSString *str)
|
||||
{
|
||||
NSTextField *tf;
|
||||
|
||||
tf = [[NSTextField alloc] initWithFrame:NSZeroRect];
|
||||
[tf setStringValue:str];
|
||||
[tf setEditable:NO];
|
||||
[tf setSelectable:NO];
|
||||
[tf setDrawsBackground:NO];
|
||||
finishNewTextField(tf, NO);
|
||||
return tf;
|
||||
}
|
||||
|
||||
uiLabel *uiNewLabel(const char *text)
|
||||
{
|
||||
uiLabel *l;
|
||||
|
||||
uiDarwinNewControl(uiLabel, l);
|
||||
|
||||
l->textfield = newLabel(toNSString(text));
|
||||
|
||||
return l;
|
||||
}
|
||||
Vendored
+239
@@ -0,0 +1,239 @@
|
||||
// 6 april 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
static BOOL canQuit = NO;
|
||||
static NSAutoreleasePool *globalPool;
|
||||
static applicationClass *app;
|
||||
static appDelegate *delegate;
|
||||
|
||||
static BOOL (^isRunning)(void);
|
||||
static BOOL stepsIsRunning;
|
||||
|
||||
@implementation applicationClass
|
||||
|
||||
- (void)sendEvent:(NSEvent *)e
|
||||
{
|
||||
if (sendAreaEvents(e) != 0)
|
||||
return;
|
||||
[super sendEvent:e];
|
||||
}
|
||||
|
||||
// NSColorPanel always sends changeColor: to the first responder regardless of whether there's a target set on it
|
||||
// we can override it here (see colorbutton.m)
|
||||
// thanks to mikeash in irc.freenode.net/#macdev for informing me this is how the first responder chain is initiated
|
||||
// it turns out NSFontManager also sends changeFont: through this; let's inhibit that here too (see fontbutton.m)
|
||||
- (BOOL)sendAction:(SEL)sel to:(id)to from:(id)from
|
||||
{
|
||||
if (colorButtonInhibitSendAction(sel, from, to))
|
||||
return NO;
|
||||
if (fontButtonInhibitSendAction(sel, from, to))
|
||||
return NO;
|
||||
return [super sendAction:sel to:to from:from];
|
||||
}
|
||||
|
||||
// likewise, NSFontManager also sends NSFontPanelValidation messages to the first responder, however it does NOT use sendAction:from:to:!
|
||||
// instead, it uses this one (thanks swillits in irc.freenode.net/#macdev)
|
||||
// we also need to override it (see fontbutton.m)
|
||||
- (id)targetForAction:(SEL)sel to:(id)to from:(id)from
|
||||
{
|
||||
id override;
|
||||
|
||||
if (fontButtonOverrideTargetForAction(sel, from, to, &override))
|
||||
return override;
|
||||
return [super targetForAction:sel to:to from:from];
|
||||
}
|
||||
|
||||
// hey look! we're overriding terminate:!
|
||||
// we're going to make sure we can go back to main() whether Cocoa likes it or not!
|
||||
// and just how are we going to do that, hm?
|
||||
// (note: this is called after applicationShouldTerminate:)
|
||||
- (void)terminate:(id)sender
|
||||
{
|
||||
// yes that's right folks: DO ABSOLUTELY NOTHING.
|
||||
// the magic is [NSApp run] will just... stop.
|
||||
|
||||
// well let's not do nothing; let's actually quit our graceful way
|
||||
NSEvent *e;
|
||||
|
||||
if (!canQuit)
|
||||
implbug("call to [NSApp terminate:] when not ready to terminate; definitely contact andlabs");
|
||||
|
||||
[realNSApp() stop:realNSApp()];
|
||||
// stop: won't register until another event has passed; let's synthesize one
|
||||
e = [NSEvent otherEventWithType:NSApplicationDefined
|
||||
location:NSZeroPoint
|
||||
modifierFlags:0
|
||||
timestamp:[[NSProcessInfo processInfo] systemUptime]
|
||||
windowNumber:0
|
||||
context:[NSGraphicsContext currentContext]
|
||||
subtype:0
|
||||
data1:0
|
||||
data2:0];
|
||||
[realNSApp() postEvent:e atStart:NO]; // let pending events take priority (this is what PostQuitMessage() on Windows does so we have to do it here too for parity; thanks to mikeash in irc.freenode.net/#macdev for confirming that this parameter should indeed be NO)
|
||||
|
||||
// and in case uiMainSteps() was called
|
||||
stepsIsRunning = NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation appDelegate
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
// Apple docs: "Don't Use Accessor Methods in Initializer Methods and dealloc"
|
||||
[_menuManager release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)app
|
||||
{
|
||||
// for debugging
|
||||
NSLog(@"in applicationShouldTerminate:");
|
||||
if (shouldQuit()) {
|
||||
canQuit = YES;
|
||||
// this will call terminate:, which is the same as uiQuit()
|
||||
return NSTerminateNow;
|
||||
}
|
||||
return NSTerminateCancel;
|
||||
}
|
||||
|
||||
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
uiInitOptions options;
|
||||
|
||||
const char *uiInit(uiInitOptions *o)
|
||||
{
|
||||
@autoreleasepool {
|
||||
options = *o;
|
||||
app = [[applicationClass sharedApplication] retain];
|
||||
// don't check for a NO return; something (launch services?) causes running from application bundles to always return NO when asking to change activation policy, even if the change is to the same activation policy!
|
||||
// see https://github.com/andlabs/ui/issues/6
|
||||
[realNSApp() setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
delegate = [appDelegate new];
|
||||
[realNSApp() setDelegate:delegate];
|
||||
|
||||
initAlloc();
|
||||
|
||||
// always do this so we always have an application menu
|
||||
appDelegate().menuManager = [[menuManager new] autorelease];
|
||||
[realNSApp() setMainMenu:[appDelegate().menuManager makeMenubar]];
|
||||
|
||||
setupFontPanel();
|
||||
}
|
||||
|
||||
globalPool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void uiUninit(void)
|
||||
{
|
||||
if (!globalPool) {
|
||||
userbug("You must call uiInit() first!");
|
||||
}
|
||||
[globalPool release];
|
||||
|
||||
@autoreleasepool {
|
||||
[delegate release];
|
||||
[realNSApp() setDelegate:nil];
|
||||
[app release];
|
||||
uninitAlloc();
|
||||
}
|
||||
}
|
||||
|
||||
void uiFreeInitError(const char *err)
|
||||
{
|
||||
}
|
||||
|
||||
void uiMain(void)
|
||||
{
|
||||
isRunning = ^{
|
||||
return [realNSApp() isRunning];
|
||||
};
|
||||
[realNSApp() run];
|
||||
}
|
||||
|
||||
void uiMainSteps(void)
|
||||
{
|
||||
// SDL does this and it seems to be necessary for the menubar to work (see #182)
|
||||
[realNSApp() finishLaunching];
|
||||
isRunning = ^{
|
||||
return stepsIsRunning;
|
||||
};
|
||||
stepsIsRunning = YES;
|
||||
}
|
||||
|
||||
int uiMainStep(int wait)
|
||||
{
|
||||
struct nextEventArgs nea;
|
||||
|
||||
nea.mask = NSAnyEventMask;
|
||||
|
||||
// ProPuke did this in his original PR requesting this
|
||||
// I'm not sure if this will work, but I assume it will...
|
||||
nea.duration = [NSDate distantPast];
|
||||
if (wait) // but this is normal so it will work
|
||||
nea.duration = [NSDate distantFuture];
|
||||
|
||||
nea.mode = NSDefaultRunLoopMode;
|
||||
nea.dequeue = YES;
|
||||
|
||||
return mainStep(&nea, ^(NSEvent *e) {
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
// see also:
|
||||
// - http://www.cocoawithlove.com/2009/01/demystifying-nsapplication-by.html
|
||||
// - https://github.com/gnustep/gui/blob/master/Source/NSApplication.m
|
||||
int mainStep(struct nextEventArgs *nea, BOOL (^interceptEvent)(NSEvent *e))
|
||||
{
|
||||
NSDate *expire;
|
||||
NSEvent *e;
|
||||
NSEventType type;
|
||||
|
||||
@autoreleasepool {
|
||||
if (!isRunning())
|
||||
return 0;
|
||||
|
||||
e = [realNSApp() nextEventMatchingMask:nea->mask
|
||||
untilDate:nea->duration
|
||||
inMode:nea->mode
|
||||
dequeue:nea->dequeue];
|
||||
if (e == nil)
|
||||
return 1;
|
||||
|
||||
type = [e type];
|
||||
if (!interceptEvent(e))
|
||||
[realNSApp() sendEvent:e];
|
||||
[realNSApp() updateWindows];
|
||||
|
||||
// GNUstep does this
|
||||
// it also updates the Services menu but there doesn't seem to be a public API for that so
|
||||
if (type != NSPeriodic && type != NSMouseMoved)
|
||||
[[realNSApp() mainMenu] update];
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
void uiQuit(void)
|
||||
{
|
||||
canQuit = YES;
|
||||
[realNSApp() terminate:realNSApp()];
|
||||
}
|
||||
|
||||
// thanks to mikeash in irc.freenode.net/#macdev for suggesting the use of Grand Central Dispatch for this
|
||||
// LONGTERM will dispatch_get_main_queue() break after _CFRunLoopSetCurrent()?
|
||||
void uiQueueMain(void (*f)(void *data), void *data)
|
||||
{
|
||||
// dispatch_get_main_queue() is a serial queue so it will not execute multiple uiQueueMain() functions concurrently
|
||||
// the signature of f matches dispatch_function_t
|
||||
dispatch_async_f(dispatch_get_main_queue(), data, f);
|
||||
}
|
||||
Vendored
+59
@@ -0,0 +1,59 @@
|
||||
// 17 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// unfortunately NSMutableDictionary copies its keys, meaning we can't use it for pointers
|
||||
// hence, this file
|
||||
// we could expose a NSMapTable directly, but let's treat all pointers as opaque and hide the implementation, just to be safe and prevent even more rewrites later
|
||||
struct mapTable {
|
||||
NSMapTable *m;
|
||||
};
|
||||
|
||||
struct mapTable *newMap(void)
|
||||
{
|
||||
struct mapTable *m;
|
||||
|
||||
m = uiNew(struct mapTable);
|
||||
m->m = [[NSMapTable alloc] initWithKeyOptions:(NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality)
|
||||
valueOptions:(NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality)
|
||||
capacity:0];
|
||||
return m;
|
||||
}
|
||||
|
||||
void mapDestroy(struct mapTable *m)
|
||||
{
|
||||
if ([m->m count] != 0)
|
||||
implbug("attempt to destroy map with items inside");
|
||||
[m->m release];
|
||||
uiFree(m);
|
||||
}
|
||||
|
||||
void *mapGet(struct mapTable *m, void *key)
|
||||
{
|
||||
return NSMapGet(m->m, key);
|
||||
}
|
||||
|
||||
void mapSet(struct mapTable *m, void *key, void *value)
|
||||
{
|
||||
NSMapInsert(m->m, key, value);
|
||||
}
|
||||
|
||||
void mapDelete(struct mapTable *m, void *key)
|
||||
{
|
||||
NSMapRemove(m->m, key);
|
||||
}
|
||||
|
||||
void mapWalk(struct mapTable *m, void (*f)(void *key, void *value))
|
||||
{
|
||||
NSMapEnumerator e = NSEnumerateMapTable(m->m);
|
||||
void *k = NULL;
|
||||
void *v = NULL;
|
||||
while (NSNextMapEnumeratorPair(&e, &k, &v)) {
|
||||
f(k, v);
|
||||
}
|
||||
NSEndMapTableEnumeration(&e);
|
||||
}
|
||||
|
||||
void mapReset(struct mapTable *m)
|
||||
{
|
||||
NSResetMapTable(m->m);
|
||||
}
|
||||
Vendored
+368
@@ -0,0 +1,368 @@
|
||||
// 28 april 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
static NSMutableArray *menus = nil;
|
||||
static BOOL menusFinalized = NO;
|
||||
|
||||
struct uiMenu {
|
||||
NSMenu *menu;
|
||||
NSMenuItem *item;
|
||||
NSMutableArray *items;
|
||||
};
|
||||
|
||||
struct uiMenuItem {
|
||||
NSMenuItem *item;
|
||||
int type;
|
||||
BOOL disabled;
|
||||
void (*onClicked)(uiMenuItem *, uiWindow *, void *);
|
||||
void *onClickedData;
|
||||
};
|
||||
|
||||
enum {
|
||||
typeRegular,
|
||||
typeCheckbox,
|
||||
typeQuit,
|
||||
typePreferences,
|
||||
typeAbout,
|
||||
typeSeparator,
|
||||
};
|
||||
|
||||
static void mapItemReleaser(void *key, void *value)
|
||||
{
|
||||
uiMenuItem *item;
|
||||
|
||||
item = (uiMenuItem *)value;
|
||||
[item->item release];
|
||||
}
|
||||
|
||||
@implementation menuManager
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self->items = newMap();
|
||||
self->hasQuit = NO;
|
||||
self->hasPreferences = NO;
|
||||
self->hasAbout = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
mapWalk(self->items, mapItemReleaser);
|
||||
mapReset(self->items);
|
||||
mapDestroy(self->items);
|
||||
uninitMenus();
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (IBAction)onClicked:(id)sender
|
||||
{
|
||||
uiMenuItem *item;
|
||||
|
||||
item = (uiMenuItem *) mapGet(self->items, sender);
|
||||
if (item->type == typeCheckbox)
|
||||
uiMenuItemSetChecked(item, !uiMenuItemChecked(item));
|
||||
// use the key window as the source of the menu event; it's the active window
|
||||
(*(item->onClicked))(item, windowFromNSWindow([realNSApp() keyWindow]), item->onClickedData);
|
||||
}
|
||||
|
||||
- (IBAction)onQuitClicked:(id)sender
|
||||
{
|
||||
if (shouldQuit())
|
||||
uiQuit();
|
||||
}
|
||||
|
||||
- (void)register:(NSMenuItem *)item to:(uiMenuItem *)smi
|
||||
{
|
||||
switch (smi->type) {
|
||||
case typeQuit:
|
||||
if (self->hasQuit)
|
||||
userbug("You can't have multiple Quit menu items in one program.");
|
||||
self->hasQuit = YES;
|
||||
break;
|
||||
case typePreferences:
|
||||
if (self->hasPreferences)
|
||||
userbug("You can't have multiple Preferences menu items in one program.");
|
||||
self->hasPreferences = YES;
|
||||
break;
|
||||
case typeAbout:
|
||||
if (self->hasAbout)
|
||||
userbug("You can't have multiple About menu items in one program.");
|
||||
self->hasAbout = YES;
|
||||
break;
|
||||
}
|
||||
mapSet(self->items, item, smi);
|
||||
}
|
||||
|
||||
// on OS X there are two ways to handle menu items being enabled or disabled: automatically and manually
|
||||
// unfortunately, the application menu requires automatic menu handling for the Hide, Hide Others, and Show All items to work correctly
|
||||
// therefore, we have to handle enabling of the other options ourselves
|
||||
- (BOOL)validateMenuItem:(NSMenuItem *)item
|
||||
{
|
||||
uiMenuItem *smi;
|
||||
|
||||
// disable the special items if they aren't present
|
||||
if (item == self.quitItem && !self->hasQuit)
|
||||
return NO;
|
||||
if (item == self.preferencesItem && !self->hasPreferences)
|
||||
return NO;
|
||||
if (item == self.aboutItem && !self->hasAbout)
|
||||
return NO;
|
||||
// then poll the item's enabled/disabled state
|
||||
smi = (uiMenuItem *) mapGet(self->items, item);
|
||||
return !smi->disabled;
|
||||
}
|
||||
|
||||
// Cocoa constructs the default application menu by hand for each program; that's what MainMenu.[nx]ib does
|
||||
- (void)buildApplicationMenu:(NSMenu *)menubar
|
||||
{
|
||||
NSString *appName;
|
||||
NSMenuItem *appMenuItem;
|
||||
NSMenu *appMenu;
|
||||
NSMenuItem *item;
|
||||
NSString *title;
|
||||
NSMenu *servicesMenu;
|
||||
|
||||
// note: no need to call setAppleMenu: on this anymore; see https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/#X10_6Notes
|
||||
appName = [[NSProcessInfo processInfo] processName];
|
||||
appMenuItem = [[[NSMenuItem alloc] initWithTitle:appName action:NULL keyEquivalent:@""] autorelease];
|
||||
appMenu = [[[NSMenu alloc] initWithTitle:appName] autorelease];
|
||||
[appMenuItem setSubmenu:appMenu];
|
||||
[menubar addItem:appMenuItem];
|
||||
|
||||
// first is About
|
||||
title = [@"About " stringByAppendingString:appName];
|
||||
item = [[[NSMenuItem alloc] initWithTitle:title action:@selector(onClicked:) keyEquivalent:@""] autorelease];
|
||||
[item setTarget:self];
|
||||
[appMenu addItem:item];
|
||||
self.aboutItem = item;
|
||||
|
||||
[appMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
// next is Preferences
|
||||
item = [[[NSMenuItem alloc] initWithTitle:@"Preferences…" action:@selector(onClicked:) keyEquivalent:@","] autorelease];
|
||||
[item setTarget:self];
|
||||
[appMenu addItem:item];
|
||||
self.preferencesItem = item;
|
||||
|
||||
[appMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
// next is Services
|
||||
item = [[[NSMenuItem alloc] initWithTitle:@"Services" action:NULL keyEquivalent:@""] autorelease];
|
||||
servicesMenu = [[[NSMenu alloc] initWithTitle:@"Services"] autorelease];
|
||||
[item setSubmenu:servicesMenu];
|
||||
[realNSApp() setServicesMenu:servicesMenu];
|
||||
[appMenu addItem:item];
|
||||
|
||||
[appMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
// next are the three hiding options
|
||||
title = [@"Hide " stringByAppendingString:appName];
|
||||
item = [[[NSMenuItem alloc] initWithTitle:title action:@selector(hide:) keyEquivalent:@"h"] autorelease];
|
||||
// the .xib file says they go to -1 ("First Responder", which sounds wrong...)
|
||||
// to do that, we simply leave the target as nil
|
||||
[appMenu addItem:item];
|
||||
item = [[[NSMenuItem alloc] initWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"] autorelease];
|
||||
[item setKeyEquivalentModifierMask:(NSAlternateKeyMask | NSCommandKeyMask)];
|
||||
[appMenu addItem:item];
|
||||
item = [[[NSMenuItem alloc] initWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""] autorelease];
|
||||
[appMenu addItem:item];
|
||||
|
||||
[appMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
// and finally Quit
|
||||
// DON'T use @selector(terminate:) as the action; we handle termination ourselves
|
||||
title = [@"Quit " stringByAppendingString:appName];
|
||||
item = [[[NSMenuItem alloc] initWithTitle:title action:@selector(onQuitClicked:) keyEquivalent:@"q"] autorelease];
|
||||
[item setTarget:self];
|
||||
[appMenu addItem:item];
|
||||
self.quitItem = item;
|
||||
}
|
||||
|
||||
- (NSMenu *)makeMenubar
|
||||
{
|
||||
NSMenu *menubar;
|
||||
|
||||
menubar = [[[NSMenu alloc] initWithTitle:@""] autorelease];
|
||||
[self buildApplicationMenu:menubar];
|
||||
return menubar;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static void defaultOnClicked(uiMenuItem *item, uiWindow *w, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
void uiMenuItemEnable(uiMenuItem *item)
|
||||
{
|
||||
item->disabled = NO;
|
||||
// we don't need to explicitly update the menus here; they'll be updated the next time they're opened (thanks mikeash in irc.freenode.net/#macdev)
|
||||
}
|
||||
|
||||
void uiMenuItemDisable(uiMenuItem *item)
|
||||
{
|
||||
item->disabled = YES;
|
||||
}
|
||||
|
||||
void uiMenuItemOnClicked(uiMenuItem *item, void (*f)(uiMenuItem *, uiWindow *, void *), void *data)
|
||||
{
|
||||
if (item->type == typeQuit)
|
||||
userbug("You can't call uiMenuItemOnClicked() on a Quit item; use uiOnShouldQuit() instead.");
|
||||
item->onClicked = f;
|
||||
item->onClickedData = data;
|
||||
}
|
||||
|
||||
int uiMenuItemChecked(uiMenuItem *item)
|
||||
{
|
||||
return [item->item state] != NSOffState;
|
||||
}
|
||||
|
||||
void uiMenuItemSetChecked(uiMenuItem *item, int checked)
|
||||
{
|
||||
NSInteger state;
|
||||
|
||||
state = NSOffState;
|
||||
if ([item->item state] == NSOffState)
|
||||
state = NSOnState;
|
||||
[item->item setState:state];
|
||||
}
|
||||
|
||||
static uiMenuItem *newItem(uiMenu *m, int type, const char *name)
|
||||
{
|
||||
@autoreleasepool {
|
||||
|
||||
uiMenuItem *item;
|
||||
|
||||
if (menusFinalized)
|
||||
userbug("You can't create a new menu item after menus have been finalized.");
|
||||
|
||||
item = uiNew(uiMenuItem);
|
||||
|
||||
item->type = type;
|
||||
switch (item->type) {
|
||||
case typeQuit:
|
||||
item->item = [appDelegate().menuManager.quitItem retain];
|
||||
break;
|
||||
case typePreferences:
|
||||
item->item = [appDelegate().menuManager.preferencesItem retain];
|
||||
break;
|
||||
case typeAbout:
|
||||
item->item = [appDelegate().menuManager.aboutItem retain];
|
||||
break;
|
||||
case typeSeparator:
|
||||
item->item = [[NSMenuItem separatorItem] retain];
|
||||
[m->menu addItem:item->item];
|
||||
break;
|
||||
default:
|
||||
item->item = [[NSMenuItem alloc] initWithTitle:toNSString(name) action:@selector(onClicked:) keyEquivalent:@""];
|
||||
[item->item setTarget:appDelegate().menuManager];
|
||||
[m->menu addItem:item->item];
|
||||
break;
|
||||
}
|
||||
|
||||
[appDelegate().menuManager register:item->item to:item];
|
||||
item->onClicked = defaultOnClicked;
|
||||
|
||||
[m->items addObject:[NSValue valueWithPointer:item]];
|
||||
|
||||
return item;
|
||||
|
||||
} // @autoreleasepool
|
||||
}
|
||||
|
||||
uiMenuItem *uiMenuAppendItem(uiMenu *m, const char *name)
|
||||
{
|
||||
return newItem(m, typeRegular, name);
|
||||
}
|
||||
|
||||
uiMenuItem *uiMenuAppendCheckItem(uiMenu *m, const char *name)
|
||||
{
|
||||
return newItem(m, typeCheckbox, name);
|
||||
}
|
||||
|
||||
uiMenuItem *uiMenuAppendQuitItem(uiMenu *m)
|
||||
{
|
||||
// duplicate check is in the register:to: selector
|
||||
return newItem(m, typeQuit, NULL);
|
||||
}
|
||||
|
||||
uiMenuItem *uiMenuAppendPreferencesItem(uiMenu *m)
|
||||
{
|
||||
// duplicate check is in the register:to: selector
|
||||
return newItem(m, typePreferences, NULL);
|
||||
}
|
||||
|
||||
uiMenuItem *uiMenuAppendAboutItem(uiMenu *m)
|
||||
{
|
||||
// duplicate check is in the register:to: selector
|
||||
return newItem(m, typeAbout, NULL);
|
||||
}
|
||||
|
||||
void uiMenuAppendSeparator(uiMenu *m)
|
||||
{
|
||||
newItem(m, typeSeparator, NULL);
|
||||
}
|
||||
|
||||
uiMenu *uiNewMenu(const char *name)
|
||||
{
|
||||
@autoreleasepool {
|
||||
|
||||
uiMenu *m;
|
||||
|
||||
if (menusFinalized)
|
||||
userbug("You can't create a new menu after menus have been finalized.");
|
||||
if (menus == nil)
|
||||
menus = [NSMutableArray new];
|
||||
|
||||
m = uiNew(uiMenu);
|
||||
|
||||
m->menu = [[NSMenu alloc] initWithTitle:toNSString(name)];
|
||||
// use automatic menu item enabling for all menus for consistency's sake
|
||||
|
||||
m->item = [[NSMenuItem alloc] initWithTitle:toNSString(name) action:NULL keyEquivalent:@""];
|
||||
[m->item setSubmenu:m->menu];
|
||||
|
||||
m->items = [NSMutableArray new];
|
||||
|
||||
[[realNSApp() mainMenu] addItem:m->item];
|
||||
|
||||
[menus addObject:[NSValue valueWithPointer:m]];
|
||||
|
||||
return m;
|
||||
|
||||
} // @autoreleasepool
|
||||
}
|
||||
|
||||
void finalizeMenus(void)
|
||||
{
|
||||
menusFinalized = YES;
|
||||
}
|
||||
|
||||
void uninitMenus(void)
|
||||
{
|
||||
if (menus == NULL)
|
||||
return;
|
||||
[menus enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop) {
|
||||
NSValue *v;
|
||||
uiMenu *m;
|
||||
|
||||
v = (NSValue *) obj;
|
||||
m = (uiMenu *) [v pointerValue];
|
||||
[m->items enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop) {
|
||||
NSValue *v;
|
||||
uiMenuItem *mi;
|
||||
|
||||
v = (NSValue *) obj;
|
||||
mi = (uiMenuItem *) [v pointerValue];
|
||||
uiFree(mi);
|
||||
}];
|
||||
[m->items release];
|
||||
uiFree(m);
|
||||
}];
|
||||
[menus release];
|
||||
}
|
||||
Vendored
+233
@@ -0,0 +1,233 @@
|
||||
// 8 december 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// NSTextView has no intrinsic content size by default, which wreaks havoc on a pure-Auto Layout system
|
||||
// we'll have to take over to get it to work
|
||||
// see also http://stackoverflow.com/questions/24210153/nstextview-not-properly-resizing-with-auto-layout and http://stackoverflow.com/questions/11237622/using-autolayout-with-expanding-nstextviews
|
||||
@interface intrinsicSizeTextView : NSTextView {
|
||||
uiMultilineEntry *libui_e;
|
||||
}
|
||||
- (id)initWithFrame:(NSRect)r e:(uiMultilineEntry *)e;
|
||||
@end
|
||||
|
||||
struct uiMultilineEntry {
|
||||
uiDarwinControl c;
|
||||
NSScrollView *sv;
|
||||
intrinsicSizeTextView *tv;
|
||||
struct scrollViewData *d;
|
||||
void (*onChanged)(uiMultilineEntry *, void *);
|
||||
void *onChangedData;
|
||||
BOOL changing;
|
||||
};
|
||||
|
||||
@implementation intrinsicSizeTextView
|
||||
|
||||
- (id)initWithFrame:(NSRect)r e:(uiMultilineEntry *)e
|
||||
{
|
||||
self = [super initWithFrame:r];
|
||||
if (self)
|
||||
self->libui_e = e;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSSize)intrinsicContentSize
|
||||
{
|
||||
NSTextContainer *textContainer;
|
||||
NSLayoutManager *layoutManager;
|
||||
NSRect rect;
|
||||
|
||||
textContainer = [self textContainer];
|
||||
layoutManager = [self layoutManager];
|
||||
[layoutManager ensureLayoutForTextContainer:textContainer];
|
||||
rect = [layoutManager usedRectForTextContainer:textContainer];
|
||||
return rect.size;
|
||||
}
|
||||
|
||||
- (void)didChangeText
|
||||
{
|
||||
[super didChangeText];
|
||||
[self invalidateIntrinsicContentSize];
|
||||
if (!self->libui_e->changing)
|
||||
(*(self->libui_e->onChanged))(self->libui_e, self->libui_e->onChangedData);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
uiDarwinControlAllDefaultsExceptDestroy(uiMultilineEntry, sv)
|
||||
|
||||
static void uiMultilineEntryDestroy(uiControl *c)
|
||||
{
|
||||
uiMultilineEntry *e = uiMultilineEntry(c);
|
||||
|
||||
scrollViewFreeData(e->sv, e->d);
|
||||
[e->tv release];
|
||||
[e->sv release];
|
||||
uiFreeControl(uiControl(e));
|
||||
}
|
||||
|
||||
static void defaultOnChanged(uiMultilineEntry *e, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
char *uiMultilineEntryText(uiMultilineEntry *e)
|
||||
{
|
||||
return uiDarwinNSStringToText([e->tv string]);
|
||||
}
|
||||
|
||||
void uiMultilineEntrySetText(uiMultilineEntry *e, const char *text)
|
||||
{
|
||||
[[e->tv textStorage] replaceCharactersInRange:NSMakeRange(0, [[e->tv string] length])
|
||||
withString:toNSString(text)];
|
||||
// must be called explicitly according to the documentation of shouldChangeTextInRange:replacementString:
|
||||
e->changing = YES;
|
||||
[e->tv didChangeText];
|
||||
e->changing = NO;
|
||||
}
|
||||
|
||||
// TODO scroll to end?
|
||||
void uiMultilineEntryAppend(uiMultilineEntry *e, const char *text)
|
||||
{
|
||||
[[e->tv textStorage] replaceCharactersInRange:NSMakeRange([[e->tv string] length], 0)
|
||||
withString:toNSString(text)];
|
||||
e->changing = YES;
|
||||
[e->tv didChangeText];
|
||||
e->changing = NO;
|
||||
}
|
||||
|
||||
void uiMultilineEntryOnChanged(uiMultilineEntry *e, void (*f)(uiMultilineEntry *e, void *data), void *data)
|
||||
{
|
||||
e->onChanged = f;
|
||||
e->onChangedData = data;
|
||||
}
|
||||
|
||||
int uiMultilineEntryReadOnly(uiMultilineEntry *e)
|
||||
{
|
||||
return [e->tv isEditable] == NO;
|
||||
}
|
||||
|
||||
void uiMultilineEntrySetReadOnly(uiMultilineEntry *e, int readonly)
|
||||
{
|
||||
BOOL editable;
|
||||
|
||||
editable = YES;
|
||||
if (readonly)
|
||||
editable = NO;
|
||||
[e->tv setEditable:editable];
|
||||
}
|
||||
|
||||
static uiMultilineEntry *finishMultilineEntry(BOOL hscroll)
|
||||
{
|
||||
uiMultilineEntry *e;
|
||||
NSFont *font;
|
||||
struct scrollViewCreateParams p;
|
||||
|
||||
uiDarwinNewControl(uiMultilineEntry, e);
|
||||
|
||||
e->tv = [[intrinsicSizeTextView alloc] initWithFrame:NSZeroRect e:e];
|
||||
|
||||
// verified against Interface Builder for a sufficiently customized text view
|
||||
|
||||
// NSText properties:
|
||||
// this is what Interface Builder sets the background color to
|
||||
[e->tv setBackgroundColor:[NSColor colorWithCalibratedWhite:1.0 alpha:1.0]];
|
||||
[e->tv setDrawsBackground:YES];
|
||||
[e->tv setEditable:YES];
|
||||
[e->tv setSelectable:YES];
|
||||
[e->tv setFieldEditor:NO];
|
||||
[e->tv setRichText:NO];
|
||||
[e->tv setImportsGraphics:NO];
|
||||
[e->tv setUsesFontPanel:NO];
|
||||
[e->tv setRulerVisible:NO];
|
||||
// we'll handle font last
|
||||
// while setAlignment: has been around since 10.0, the named constant "NSTextAlignmentNatural" seems to have only been introduced in 10.11
|
||||
#define ourNSTextAlignmentNatural 4
|
||||
[e->tv setAlignment:ourNSTextAlignmentNatural];
|
||||
// textColor is set to nil, just keep the dfault
|
||||
[e->tv setBaseWritingDirection:NSWritingDirectionNatural];
|
||||
[e->tv setHorizontallyResizable:NO];
|
||||
[e->tv setVerticallyResizable:YES];
|
||||
|
||||
// NSTextView properties:
|
||||
[e->tv setAllowsDocumentBackgroundColorChange:NO];
|
||||
[e->tv setAllowsUndo:YES];
|
||||
// default paragraph style is nil; keep default
|
||||
[e->tv setAllowsImageEditing:NO];
|
||||
[e->tv setAutomaticQuoteSubstitutionEnabled:NO];
|
||||
[e->tv setAutomaticLinkDetectionEnabled:NO];
|
||||
[e->tv setDisplaysLinkToolTips:YES];
|
||||
[e->tv setUsesRuler:NO];
|
||||
[e->tv setUsesInspectorBar:NO];
|
||||
[e->tv setSelectionGranularity:NSSelectByCharacter];
|
||||
// there is a dedicated named insertion point color but oh well
|
||||
[e->tv setInsertionPointColor:[NSColor controlTextColor]];
|
||||
// typing attributes is nil; keep default (we change it below for fonts though)
|
||||
[e->tv setSmartInsertDeleteEnabled:NO];
|
||||
[e->tv setContinuousSpellCheckingEnabled:NO];
|
||||
[e->tv setGrammarCheckingEnabled:NO];
|
||||
[e->tv setUsesFindPanel:YES];
|
||||
[e->tv setEnabledTextCheckingTypes:0];
|
||||
[e->tv setAutomaticDashSubstitutionEnabled:NO];
|
||||
[e->tv setAutomaticDataDetectionEnabled:NO];
|
||||
[e->tv setAutomaticSpellingCorrectionEnabled:NO];
|
||||
[e->tv setAutomaticTextReplacementEnabled:NO];
|
||||
[e->tv setUsesFindBar:NO];
|
||||
[e->tv setIncrementalSearchingEnabled:NO];
|
||||
|
||||
// NSTextContainer properties:
|
||||
[[e->tv textContainer] setWidthTracksTextView:YES];
|
||||
[[e->tv textContainer] setHeightTracksTextView:NO];
|
||||
|
||||
// NSLayoutManager properties:
|
||||
[[e->tv layoutManager] setAllowsNonContiguousLayout:YES];
|
||||
|
||||
// now just to be safe; this will do some of the above but whatever
|
||||
disableAutocorrect(e->tv);
|
||||
|
||||
// see https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/TextUILayer/Tasks/TextInScrollView.html
|
||||
// notice we don't use the Auto Layout code; see scrollview.m for more details
|
||||
[e->tv setMaxSize:NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX)];
|
||||
[e->tv setVerticallyResizable:YES];
|
||||
[e->tv setHorizontallyResizable:hscroll];
|
||||
if (hscroll) {
|
||||
[e->tv setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
|
||||
[[e->tv textContainer] setWidthTracksTextView:NO];
|
||||
} else {
|
||||
[e->tv setAutoresizingMask:NSViewWidthSizable];
|
||||
[[e->tv textContainer] setWidthTracksTextView:YES];
|
||||
}
|
||||
[[e->tv textContainer] setContainerSize:NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX)];
|
||||
|
||||
// don't use uiDarwinSetControlFont() directly; we have to do a little extra work to set the font
|
||||
font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]];
|
||||
[e->tv setTypingAttributes:[NSDictionary
|
||||
dictionaryWithObject:font
|
||||
forKey:NSFontAttributeName]];
|
||||
// e->tv font from Interface Builder is nil, but setFont:nil throws an exception
|
||||
// let's just set it to the standard control font anyway, just to be safe
|
||||
[e->tv setFont:font];
|
||||
|
||||
memset(&p, 0, sizeof (struct scrollViewCreateParams));
|
||||
p.DocumentView = e->tv;
|
||||
// this is what Interface Builder sets it to
|
||||
p.BackgroundColor = [NSColor colorWithCalibratedWhite:1.0 alpha:1.0];
|
||||
p.DrawsBackground = YES;
|
||||
p.Bordered = YES;
|
||||
p.HScroll = hscroll;
|
||||
p.VScroll = YES;
|
||||
e->sv = mkScrollView(&p, &(e->d));
|
||||
|
||||
uiMultilineEntryOnChanged(e, defaultOnChanged, NULL);
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
uiMultilineEntry *uiNewMultilineEntry(void)
|
||||
{
|
||||
return finishMultilineEntry(NO);
|
||||
}
|
||||
|
||||
uiMultilineEntry *uiNewNonWrappingMultilineEntry(void)
|
||||
{
|
||||
return finishMultilineEntry(YES);
|
||||
}
|
||||
Vendored
+78
@@ -0,0 +1,78 @@
|
||||
// 14 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// NSProgressIndicator has no intrinsic width by default; use the default width in Interface Builder
|
||||
#define progressIndicatorWidth 100
|
||||
|
||||
@interface intrinsicWidthNSProgressIndicator : NSProgressIndicator
|
||||
@end
|
||||
|
||||
@implementation intrinsicWidthNSProgressIndicator
|
||||
|
||||
- (NSSize)intrinsicContentSize
|
||||
{
|
||||
NSSize s;
|
||||
|
||||
s = [super intrinsicContentSize];
|
||||
s.width = progressIndicatorWidth;
|
||||
return s;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
struct uiProgressBar {
|
||||
uiDarwinControl c;
|
||||
NSProgressIndicator *pi;
|
||||
};
|
||||
|
||||
uiDarwinControlAllDefaults(uiProgressBar, pi)
|
||||
|
||||
int uiProgressBarValue(uiProgressBar *p)
|
||||
{
|
||||
if ([p->pi isIndeterminate])
|
||||
return -1;
|
||||
return [p->pi doubleValue];
|
||||
}
|
||||
|
||||
void uiProgressBarSetValue(uiProgressBar *p, int value)
|
||||
{
|
||||
if (value == -1) {
|
||||
[p->pi setIndeterminate:YES];
|
||||
[p->pi startAnimation:p->pi];
|
||||
return;
|
||||
}
|
||||
|
||||
if ([p->pi isIndeterminate]) {
|
||||
[p->pi setIndeterminate:NO];
|
||||
[p->pi stopAnimation:p->pi];
|
||||
}
|
||||
|
||||
if (value < 0 || value > 100)
|
||||
userbug("Value %d out of range for a uiProgressBar.", value);
|
||||
|
||||
// on 10.8 there's an animation when the progress bar increases, just like with Aero
|
||||
if (value == 100) {
|
||||
[p->pi setMaxValue:101];
|
||||
[p->pi setDoubleValue:101];
|
||||
[p->pi setDoubleValue:100];
|
||||
[p->pi setMaxValue:100];
|
||||
return;
|
||||
}
|
||||
[p->pi setDoubleValue:((double) (value + 1))];
|
||||
[p->pi setDoubleValue:((double) value)];
|
||||
}
|
||||
|
||||
uiProgressBar *uiNewProgressBar(void)
|
||||
{
|
||||
uiProgressBar *p;
|
||||
|
||||
uiDarwinNewControl(uiProgressBar, p);
|
||||
|
||||
p->pi = [[intrinsicWidthNSProgressIndicator alloc] initWithFrame:NSZeroRect];
|
||||
[p->pi setControlSize:NSRegularControlSize];
|
||||
[p->pi setBezeled:YES];
|
||||
[p->pi setStyle:NSProgressIndicatorBarStyle];
|
||||
[p->pi setIndeterminate:NO];
|
||||
|
||||
return p;
|
||||
}
|
||||
Vendored
+207
@@ -0,0 +1,207 @@
|
||||
// 14 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// TODO resizing the controlgallery vertically causes the third button to still resize :|
|
||||
|
||||
// In the old days you would use a NSMatrix for this; as of OS X 10.8 this was deprecated and now you need just a bunch of NSButtons with the same superview AND same action method.
|
||||
// This is documented on the NSMatrix page, but the rest of the OS X documentation says to still use NSMatrix.
|
||||
// NSMatrix has weird quirks anyway...
|
||||
|
||||
// LONGTERM 6 units of spacing between buttons, as suggested by Interface Builder?
|
||||
|
||||
@interface radioButtonsDelegate : NSObject {
|
||||
uiRadioButtons *libui_r;
|
||||
}
|
||||
- (id)initWithR:(uiRadioButtons *)r;
|
||||
- (IBAction)onClicked:(id)sender;
|
||||
@end
|
||||
|
||||
struct uiRadioButtons {
|
||||
uiDarwinControl c;
|
||||
NSView *view;
|
||||
NSMutableArray *buttons;
|
||||
NSMutableArray *constraints;
|
||||
NSLayoutConstraint *lastv;
|
||||
radioButtonsDelegate *delegate;
|
||||
void (*onSelected)(uiRadioButtons *, void *);
|
||||
void *onSelectedData;
|
||||
};
|
||||
|
||||
@implementation radioButtonsDelegate
|
||||
|
||||
- (id)initWithR:(uiRadioButtons *)r
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
self->libui_r = r;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (IBAction)onClicked:(id)sender
|
||||
{
|
||||
uiRadioButtons *r = self->libui_r;
|
||||
|
||||
(*(r->onSelected))(r, r->onSelectedData);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
uiDarwinControlAllDefaultsExceptDestroy(uiRadioButtons, view)
|
||||
|
||||
static void defaultOnSelected(uiRadioButtons *r, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
static void uiRadioButtonsDestroy(uiControl *c)
|
||||
{
|
||||
uiRadioButtons *r = uiRadioButtons(c);
|
||||
NSButton *b;
|
||||
|
||||
// drop the constraints
|
||||
[r->view removeConstraints:r->constraints];
|
||||
[r->constraints release];
|
||||
if (r->lastv != nil)
|
||||
[r->lastv release];
|
||||
// destroy the buttons
|
||||
for (b in r->buttons) {
|
||||
[b setTarget:nil];
|
||||
[b removeFromSuperview];
|
||||
}
|
||||
[r->buttons release];
|
||||
// destroy the delegate
|
||||
[r->delegate release];
|
||||
// and destroy ourselves
|
||||
[r->view release];
|
||||
uiFreeControl(uiControl(r));
|
||||
}
|
||||
|
||||
static NSButton *buttonAt(uiRadioButtons *r, int n)
|
||||
{
|
||||
return (NSButton *) [r->buttons objectAtIndex:n];
|
||||
}
|
||||
|
||||
void uiRadioButtonsAppend(uiRadioButtons *r, const char *text)
|
||||
{
|
||||
NSButton *b, *b2;
|
||||
NSLayoutConstraint *constraint;
|
||||
|
||||
b = [[NSButton alloc] initWithFrame:NSZeroRect];
|
||||
[b setTitle:toNSString(text)];
|
||||
[b setButtonType:NSRadioButton];
|
||||
// doesn't seem to have an associated bezel style
|
||||
[b setBordered:NO];
|
||||
[b setTransparent:NO];
|
||||
uiDarwinSetControlFont(b, NSRegularControlSize);
|
||||
[b setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
|
||||
[b setTarget:r->delegate];
|
||||
[b setAction:@selector(onClicked:)];
|
||||
|
||||
[r->buttons addObject:b];
|
||||
[r->view addSubview:b];
|
||||
|
||||
// pin horizontally to the edges of the superview
|
||||
constraint = mkConstraint(b, NSLayoutAttributeLeading,
|
||||
NSLayoutRelationEqual,
|
||||
r->view, NSLayoutAttributeLeading,
|
||||
1, 0,
|
||||
@"uiRadioButtons button leading constraint");
|
||||
[r->view addConstraint:constraint];
|
||||
[r->constraints addObject:constraint];
|
||||
constraint = mkConstraint(b, NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationEqual,
|
||||
r->view, NSLayoutAttributeTrailing,
|
||||
1, 0,
|
||||
@"uiRadioButtons button trailing constraint");
|
||||
[r->view addConstraint:constraint];
|
||||
[r->constraints addObject:constraint];
|
||||
|
||||
// if this is the first view, pin it to the top
|
||||
// otherwise pin to the bottom of the last
|
||||
if ([r->buttons count] == 1)
|
||||
constraint = mkConstraint(b, NSLayoutAttributeTop,
|
||||
NSLayoutRelationEqual,
|
||||
r->view, NSLayoutAttributeTop,
|
||||
1, 0,
|
||||
@"uiRadioButtons first button top constraint");
|
||||
else {
|
||||
b2 = buttonAt(r, [r->buttons count] - 2);
|
||||
constraint = mkConstraint(b, NSLayoutAttributeTop,
|
||||
NSLayoutRelationEqual,
|
||||
b2, NSLayoutAttributeBottom,
|
||||
1, 0,
|
||||
@"uiRadioButtons non-first button top constraint");
|
||||
}
|
||||
[r->view addConstraint:constraint];
|
||||
[r->constraints addObject:constraint];
|
||||
|
||||
// if there is a previous bottom constraint, remove it
|
||||
if (r->lastv != nil) {
|
||||
[r->view removeConstraint:r->lastv];
|
||||
[r->constraints removeObject:r->lastv];
|
||||
[r->lastv release];
|
||||
}
|
||||
|
||||
// and make the new bottom constraint
|
||||
r->lastv = mkConstraint(b, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationEqual,
|
||||
r->view, NSLayoutAttributeBottom,
|
||||
1, 0,
|
||||
@"uiRadioButtons last button bottom constraint");
|
||||
[r->view addConstraint:r->lastv];
|
||||
[r->constraints addObject:r->lastv];
|
||||
[r->lastv retain];
|
||||
}
|
||||
|
||||
int uiRadioButtonsSelected(uiRadioButtons *r)
|
||||
{
|
||||
NSButton *b;
|
||||
NSUInteger i;
|
||||
|
||||
for (i = 0; i < [r->buttons count]; i++) {
|
||||
b = (NSButton *) [r->buttons objectAtIndex:i];
|
||||
if ([b state] == NSOnState)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void uiRadioButtonsSetSelected(uiRadioButtons *r, int n)
|
||||
{
|
||||
NSButton *b;
|
||||
NSInteger state;
|
||||
|
||||
state = NSOnState;
|
||||
if (n == -1) {
|
||||
n = uiRadioButtonsSelected(r);
|
||||
if (n == -1) // from nothing to nothing; do nothing
|
||||
return;
|
||||
state = NSOffState;
|
||||
}
|
||||
b = (NSButton *) [r->buttons objectAtIndex:n];
|
||||
[b setState:state];
|
||||
}
|
||||
|
||||
void uiRadioButtonsOnSelected(uiRadioButtons *r, void (*f)(uiRadioButtons *, void *), void *data)
|
||||
{
|
||||
r->onSelected = f;
|
||||
r->onSelectedData = data;
|
||||
}
|
||||
|
||||
uiRadioButtons *uiNewRadioButtons(void)
|
||||
{
|
||||
uiRadioButtons *r;
|
||||
|
||||
uiDarwinNewControl(uiRadioButtons, r);
|
||||
|
||||
r->view = [[NSView alloc] initWithFrame:NSZeroRect];
|
||||
r->buttons = [NSMutableArray new];
|
||||
r->constraints = [NSMutableArray new];
|
||||
|
||||
r->delegate = [[radioButtonsDelegate alloc] initWithR:r];
|
||||
|
||||
uiRadioButtonsOnSelected(r, defaultOnSelected, NULL);
|
||||
|
||||
return r;
|
||||
}
|
||||
Vendored
+61
@@ -0,0 +1,61 @@
|
||||
// 27 may 2016
|
||||
#include "uipriv_darwin.h"
|
||||
|
||||
// see http://stackoverflow.com/questions/37979445/how-do-i-properly-set-up-a-scrolling-nstableview-using-auto-layout-what-ive-tr for why we don't use auto layout
|
||||
// TODO do the same with uiGroup and uiTab?
|
||||
|
||||
struct scrollViewData {
|
||||
BOOL hscroll;
|
||||
BOOL vscroll;
|
||||
};
|
||||
|
||||
NSScrollView *mkScrollView(struct scrollViewCreateParams *p, struct scrollViewData **dout)
|
||||
{
|
||||
NSScrollView *sv;
|
||||
NSBorderType border;
|
||||
struct scrollViewData *d;
|
||||
|
||||
sv = [[NSScrollView alloc] initWithFrame:NSZeroRect];
|
||||
if (p->BackgroundColor != nil)
|
||||
[sv setBackgroundColor:p->BackgroundColor];
|
||||
[sv setDrawsBackground:p->DrawsBackground];
|
||||
border = NSNoBorder;
|
||||
if (p->Bordered)
|
||||
border = NSBezelBorder;
|
||||
// document view seems to set the cursor properly
|
||||
[sv setBorderType:border];
|
||||
[sv setAutohidesScrollers:YES];
|
||||
[sv setHasHorizontalRuler:NO];
|
||||
[sv setHasVerticalRuler:NO];
|
||||
[sv setRulersVisible:NO];
|
||||
[sv setScrollerKnobStyle:NSScrollerKnobStyleDefault];
|
||||
// the scroller style is documented as being set by default for us
|
||||
// LONGTERM verify line and page for programmatically created NSTableView
|
||||
[sv setScrollsDynamically:YES];
|
||||
[sv setFindBarPosition:NSScrollViewFindBarPositionAboveContent];
|
||||
[sv setUsesPredominantAxisScrolling:NO];
|
||||
[sv setHorizontalScrollElasticity:NSScrollElasticityAutomatic];
|
||||
[sv setVerticalScrollElasticity:NSScrollElasticityAutomatic];
|
||||
[sv setAllowsMagnification:NO];
|
||||
|
||||
[sv setDocumentView:p->DocumentView];
|
||||
d = uiNew(struct scrollViewData);
|
||||
scrollViewSetScrolling(sv, d, p->HScroll, p->VScroll);
|
||||
|
||||
*dout = d;
|
||||
return sv;
|
||||
}
|
||||
|
||||
// based on http://blog.bjhomer.com/2014/08/nsscrollview-and-autolayout.html because (as pointed out there) Apple's official guide is really only for iOS
|
||||
void scrollViewSetScrolling(NSScrollView *sv, struct scrollViewData *d, BOOL hscroll, BOOL vscroll)
|
||||
{
|
||||
d->hscroll = hscroll;
|
||||
[sv setHasHorizontalScroller:d->hscroll];
|
||||
d->vscroll = vscroll;
|
||||
[sv setHasVerticalScroller:d->vscroll];
|
||||
}
|
||||
|
||||
void scrollViewFreeData(NSScrollView *sv, struct scrollViewData *d)
|
||||
{
|
||||
uiFree(d);
|
||||
}
|
||||
Vendored
+45
@@ -0,0 +1,45 @@
|
||||
// 14 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// TODO make this intrinsic
|
||||
#define separatorWidth 96
|
||||
#define separatorHeight 96
|
||||
|
||||
struct uiSeparator {
|
||||
uiDarwinControl c;
|
||||
NSBox *box;
|
||||
};
|
||||
|
||||
uiDarwinControlAllDefaults(uiSeparator, box)
|
||||
|
||||
uiSeparator *uiNewHorizontalSeparator(void)
|
||||
{
|
||||
uiSeparator *s;
|
||||
|
||||
uiDarwinNewControl(uiSeparator, s);
|
||||
|
||||
// make the initial width >= initial height to force horizontal
|
||||
s->box = [[NSBox alloc] initWithFrame:NSMakeRect(0, 0, 100, 1)];
|
||||
[s->box setBoxType:NSBoxSeparator];
|
||||
[s->box setBorderType:NSGrooveBorder];
|
||||
[s->box setTransparent:NO];
|
||||
[s->box setTitlePosition:NSNoTitle];
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
uiSeparator *uiNewVerticalSeparator(void)
|
||||
{
|
||||
uiSeparator *s;
|
||||
|
||||
uiDarwinNewControl(uiSeparator, s);
|
||||
|
||||
// make the initial height >= initial width to force vertical
|
||||
s->box = [[NSBox alloc] initWithFrame:NSMakeRect(0, 0, 1, 100)];
|
||||
[s->box setBoxType:NSBoxSeparator];
|
||||
[s->box setBorderType:NSGrooveBorder];
|
||||
[s->box setTransparent:NO];
|
||||
[s->box setTitlePosition:NSNoTitle];
|
||||
|
||||
return s;
|
||||
}
|
||||
Vendored
+147
@@ -0,0 +1,147 @@
|
||||
// 14 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// Horizontal sliders have no intrinsic width; we'll use the default Interface Builder width for them.
|
||||
// This will also be used for the initial frame size, to ensure the slider is always horizontal (see below).
|
||||
#define sliderWidth 92
|
||||
|
||||
@interface libui_intrinsicWidthNSSlider : NSSlider
|
||||
@end
|
||||
|
||||
@implementation libui_intrinsicWidthNSSlider
|
||||
|
||||
- (NSSize)intrinsicContentSize
|
||||
{
|
||||
NSSize s;
|
||||
|
||||
s = [super intrinsicContentSize];
|
||||
s.width = sliderWidth;
|
||||
return s;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
struct uiSlider {
|
||||
uiDarwinControl c;
|
||||
NSSlider *slider;
|
||||
void (*onChanged)(uiSlider *, void *);
|
||||
void *onChangedData;
|
||||
};
|
||||
|
||||
@interface sliderDelegateClass : NSObject {
|
||||
struct mapTable *sliders;
|
||||
}
|
||||
- (IBAction)onChanged:(id)sender;
|
||||
- (void)registerSlider:(uiSlider *)b;
|
||||
- (void)unregisterSlider:(uiSlider *)b;
|
||||
@end
|
||||
|
||||
@implementation sliderDelegateClass
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
self->sliders = newMap();
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
mapDestroy(self->sliders);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (IBAction)onChanged:(id)sender
|
||||
{
|
||||
uiSlider *s;
|
||||
|
||||
s = (uiSlider *) mapGet(self->sliders, sender);
|
||||
(*(s->onChanged))(s, s->onChangedData);
|
||||
}
|
||||
|
||||
- (void)registerSlider:(uiSlider *)s
|
||||
{
|
||||
mapSet(self->sliders, s->slider, s);
|
||||
[s->slider setTarget:self];
|
||||
[s->slider setAction:@selector(onChanged:)];
|
||||
}
|
||||
|
||||
- (void)unregisterSlider:(uiSlider *)s
|
||||
{
|
||||
[s->slider setTarget:nil];
|
||||
mapDelete(self->sliders, s->slider);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static sliderDelegateClass *sliderDelegate = nil;
|
||||
|
||||
uiDarwinControlAllDefaultsExceptDestroy(uiSlider, slider)
|
||||
|
||||
static void uiSliderDestroy(uiControl *c)
|
||||
{
|
||||
uiSlider *s = uiSlider(c);
|
||||
|
||||
[sliderDelegate unregisterSlider:s];
|
||||
[s->slider release];
|
||||
uiFreeControl(uiControl(s));
|
||||
}
|
||||
|
||||
int uiSliderValue(uiSlider *s)
|
||||
{
|
||||
return [s->slider integerValue];
|
||||
}
|
||||
|
||||
void uiSliderSetValue(uiSlider *s, int value)
|
||||
{
|
||||
[s->slider setIntegerValue:value];
|
||||
}
|
||||
|
||||
void uiSliderOnChanged(uiSlider *s, void (*f)(uiSlider *, void *), void *data)
|
||||
{
|
||||
s->onChanged = f;
|
||||
s->onChangedData = data;
|
||||
}
|
||||
|
||||
static void defaultOnChanged(uiSlider *s, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
uiSlider *uiNewSlider(int min, int max)
|
||||
{
|
||||
uiSlider *s;
|
||||
NSSliderCell *cell;
|
||||
int temp;
|
||||
|
||||
if (min >= max) {
|
||||
temp = min;
|
||||
min = max;
|
||||
max = temp;
|
||||
}
|
||||
|
||||
uiDarwinNewControl(uiSlider, s);
|
||||
|
||||
// a horizontal slider is defined as one where the width > height, not by a flag
|
||||
// to be safe, don't use NSZeroRect, but make it horizontal from the get-go
|
||||
s->slider = [[libui_intrinsicWidthNSSlider alloc]
|
||||
initWithFrame:NSMakeRect(0, 0, sliderWidth, 2)];
|
||||
[s->slider setMinValue:min];
|
||||
[s->slider setMaxValue:max];
|
||||
[s->slider setAllowsTickMarkValuesOnly:NO];
|
||||
[s->slider setNumberOfTickMarks:0];
|
||||
[s->slider setTickMarkPosition:NSTickMarkAbove];
|
||||
|
||||
cell = (NSSliderCell *) [s->slider cell];
|
||||
[cell setSliderType:NSLinearSlider];
|
||||
|
||||
if (sliderDelegate == nil) {
|
||||
sliderDelegate = [[sliderDelegateClass new] autorelease];
|
||||
[delegates addObject:sliderDelegate];
|
||||
}
|
||||
[sliderDelegate registerSlider:s];
|
||||
uiSliderOnChanged(s, defaultOnChanged, NULL);
|
||||
|
||||
return s;
|
||||
}
|
||||
Vendored
+214
@@ -0,0 +1,214 @@
|
||||
// 14 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
@interface libui_spinbox : NSView<NSTextFieldDelegate> {
|
||||
NSTextField *tf;
|
||||
NSNumberFormatter *formatter;
|
||||
NSStepper *stepper;
|
||||
|
||||
NSInteger value;
|
||||
NSInteger minimum;
|
||||
NSInteger maximum;
|
||||
|
||||
uiSpinbox *spinbox;
|
||||
}
|
||||
- (id)initWithFrame:(NSRect)r spinbox:(uiSpinbox *)sb;
|
||||
// see https://github.com/andlabs/ui/issues/82
|
||||
- (NSInteger)libui_value;
|
||||
- (void)libui_setValue:(NSInteger)val;
|
||||
- (void)setMinimum:(NSInteger)min;
|
||||
- (void)setMaximum:(NSInteger)max;
|
||||
- (IBAction)stepperClicked:(id)sender;
|
||||
- (void)controlTextDidChange:(NSNotification *)note;
|
||||
@end
|
||||
|
||||
struct uiSpinbox {
|
||||
uiDarwinControl c;
|
||||
libui_spinbox *spinbox;
|
||||
void (*onChanged)(uiSpinbox *, void *);
|
||||
void *onChangedData;
|
||||
};
|
||||
|
||||
// yes folks, this varies by operating system! woo!
|
||||
// 10.10 started drawing the NSStepper one point too low, so we have to fix it up conditionally
|
||||
// TODO test this; we'll probably have to substitute 10_9
|
||||
static CGFloat stepperYDelta(void)
|
||||
{
|
||||
// via https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKit/
|
||||
if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_9)
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@implementation libui_spinbox
|
||||
|
||||
- (id)initWithFrame:(NSRect)r spinbox:(uiSpinbox *)sb
|
||||
{
|
||||
self = [super initWithFrame:r];
|
||||
if (self) {
|
||||
self->tf = newEditableTextField();
|
||||
[self->tf setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
|
||||
self->formatter = [NSNumberFormatter new];
|
||||
[self->formatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
|
||||
[self->formatter setLocalizesFormat:NO];
|
||||
[self->formatter setUsesGroupingSeparator:NO];
|
||||
[self->formatter setHasThousandSeparators:NO];
|
||||
[self->formatter setAllowsFloats:NO];
|
||||
[self->tf setFormatter:self->formatter];
|
||||
|
||||
self->stepper = [[NSStepper alloc] initWithFrame:NSZeroRect];
|
||||
[self->stepper setIncrement:1];
|
||||
[self->stepper setValueWraps:NO];
|
||||
[self->stepper setAutorepeat:YES]; // hold mouse button to step repeatedly
|
||||
[self->stepper setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
|
||||
[self->tf setDelegate:self];
|
||||
[self->stepper setTarget:self];
|
||||
[self->stepper setAction:@selector(stepperClicked:)];
|
||||
|
||||
[self addSubview:self->tf];
|
||||
[self addSubview:self->stepper];
|
||||
|
||||
[self addConstraint:mkConstraint(self->tf, NSLayoutAttributeLeading,
|
||||
NSLayoutRelationEqual,
|
||||
self, NSLayoutAttributeLeading,
|
||||
1, 0,
|
||||
@"uiSpinbox left edge")];
|
||||
[self addConstraint:mkConstraint(self->stepper, NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationEqual,
|
||||
self, NSLayoutAttributeTrailing,
|
||||
1, 0,
|
||||
@"uiSpinbox right edge")];
|
||||
[self addConstraint:mkConstraint(self->tf, NSLayoutAttributeTop,
|
||||
NSLayoutRelationEqual,
|
||||
self, NSLayoutAttributeTop,
|
||||
1, 0,
|
||||
@"uiSpinbox top edge text field")];
|
||||
[self addConstraint:mkConstraint(self->tf, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationEqual,
|
||||
self, NSLayoutAttributeBottom,
|
||||
1, 0,
|
||||
@"uiSpinbox bottom edge text field")];
|
||||
[self addConstraint:mkConstraint(self->stepper, NSLayoutAttributeTop,
|
||||
NSLayoutRelationEqual,
|
||||
self, NSLayoutAttributeTop,
|
||||
1, stepperYDelta(),
|
||||
@"uiSpinbox top edge stepper")];
|
||||
[self addConstraint:mkConstraint(self->stepper, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationEqual,
|
||||
self, NSLayoutAttributeBottom,
|
||||
1, stepperYDelta(),
|
||||
@"uiSpinbox bottom edge stepper")];
|
||||
[self addConstraint:mkConstraint(self->tf, NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationEqual,
|
||||
self->stepper, NSLayoutAttributeLeading,
|
||||
1, -3, // arbitrary amount; good enough visually (and it seems to match NSDatePicker too, at least on 10.11, which is even better)
|
||||
@"uiSpinbox space between text field and stepper")];
|
||||
|
||||
self->spinbox = sb;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self->tf setDelegate:nil];
|
||||
[self->tf removeFromSuperview];
|
||||
[self->tf release];
|
||||
[self->formatter release];
|
||||
[self->stepper setTarget:nil];
|
||||
[self->stepper removeFromSuperview];
|
||||
[self->stepper release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSInteger)libui_value
|
||||
{
|
||||
return self->value;
|
||||
}
|
||||
|
||||
- (void)libui_setValue:(NSInteger)val
|
||||
{
|
||||
self->value = val;
|
||||
if (self->value < self->minimum)
|
||||
self->value = self->minimum;
|
||||
if (self->value > self->maximum)
|
||||
self->value = self->maximum;
|
||||
[self->tf setIntegerValue:self->value];
|
||||
[self->stepper setIntegerValue:self->value];
|
||||
}
|
||||
|
||||
- (void)setMinimum:(NSInteger)min
|
||||
{
|
||||
self->minimum = min;
|
||||
[self->formatter setMinimum:[NSNumber numberWithInteger:self->minimum]];
|
||||
[self->stepper setMinValue:((double) (self->minimum))];
|
||||
}
|
||||
|
||||
- (void)setMaximum:(NSInteger)max
|
||||
{
|
||||
self->maximum = max;
|
||||
[self->formatter setMaximum:[NSNumber numberWithInteger:self->maximum]];
|
||||
[self->stepper setMaxValue:((double) (self->maximum))];
|
||||
}
|
||||
|
||||
- (IBAction)stepperClicked:(id)sender
|
||||
{
|
||||
[self libui_setValue:[self->stepper integerValue]];
|
||||
(*(self->spinbox->onChanged))(self->spinbox, self->spinbox->onChangedData);
|
||||
}
|
||||
|
||||
- (void)controlTextDidChange:(NSNotification *)note
|
||||
{
|
||||
[self libui_setValue:[self->tf integerValue]];
|
||||
(*(self->spinbox->onChanged))(self->spinbox, self->spinbox->onChangedData);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
uiDarwinControlAllDefaults(uiSpinbox, spinbox)
|
||||
|
||||
int uiSpinboxValue(uiSpinbox *s)
|
||||
{
|
||||
return [s->spinbox libui_value];
|
||||
}
|
||||
|
||||
void uiSpinboxSetValue(uiSpinbox *s, int value)
|
||||
{
|
||||
[s->spinbox libui_setValue:value];
|
||||
}
|
||||
|
||||
void uiSpinboxOnChanged(uiSpinbox *s, void (*f)(uiSpinbox *, void *), void *data)
|
||||
{
|
||||
s->onChanged = f;
|
||||
s->onChangedData = data;
|
||||
}
|
||||
|
||||
static void defaultOnChanged(uiSpinbox *s, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
uiSpinbox *uiNewSpinbox(int min, int max)
|
||||
{
|
||||
uiSpinbox *s;
|
||||
int temp;
|
||||
|
||||
if (min >= max) {
|
||||
temp = min;
|
||||
min = max;
|
||||
max = temp;
|
||||
}
|
||||
|
||||
uiDarwinNewControl(uiSpinbox, s);
|
||||
|
||||
s->spinbox = [[libui_spinbox alloc] initWithFrame:NSZeroRect spinbox:s];
|
||||
[s->spinbox setMinimum:min];
|
||||
[s->spinbox setMaximum:max];
|
||||
[s->spinbox libui_setValue:min];
|
||||
|
||||
uiSpinboxOnChanged(s, defaultOnChanged, NULL);
|
||||
|
||||
return s;
|
||||
}
|
||||
Vendored
+123
@@ -0,0 +1,123 @@
|
||||
// 26 june 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// LONGTERM restructure this whole file
|
||||
// LONGTERM explicitly document this works as we want
|
||||
// LONGTERM note that font and color buttons also do this
|
||||
|
||||
#define windowWindow(w) ((NSWindow *) uiControlHandle(uiControl(w)))
|
||||
|
||||
// source of code modal logic: http://stackoverflow.com/questions/604768/wait-for-nsalert-beginsheetmodalforwindow
|
||||
|
||||
// note: whether extensions are actually shown depends on a user setting in Finder; we can't control it here
|
||||
static void setupSavePanel(NSSavePanel *s)
|
||||
{
|
||||
[s setCanCreateDirectories:YES];
|
||||
[s setShowsHiddenFiles:YES];
|
||||
[s setExtensionHidden:NO];
|
||||
[s setCanSelectHiddenExtension:NO];
|
||||
[s setTreatsFilePackagesAsDirectories:YES];
|
||||
}
|
||||
|
||||
static char *runSavePanel(NSWindow *parent, NSSavePanel *s)
|
||||
{
|
||||
char *filename;
|
||||
|
||||
[s beginSheetModalForWindow:parent completionHandler:^(NSInteger result) {
|
||||
[realNSApp() stopModalWithCode:result];
|
||||
}];
|
||||
if ([realNSApp() runModalForWindow:s] != NSFileHandlingPanelOKButton)
|
||||
return NULL;
|
||||
filename = uiDarwinNSStringToText([[s URL] path]);
|
||||
return filename;
|
||||
}
|
||||
|
||||
char *uiOpenFile(uiWindow *parent)
|
||||
{
|
||||
NSOpenPanel *o;
|
||||
|
||||
o = [NSOpenPanel openPanel];
|
||||
[o setCanChooseFiles:YES];
|
||||
[o setCanChooseDirectories:NO];
|
||||
[o setResolvesAliases:NO];
|
||||
[o setAllowsMultipleSelection:NO];
|
||||
setupSavePanel(o);
|
||||
// panel is autoreleased
|
||||
return runSavePanel(windowWindow(parent), o);
|
||||
}
|
||||
|
||||
char *uiSaveFile(uiWindow *parent)
|
||||
{
|
||||
NSSavePanel *s;
|
||||
|
||||
s = [NSSavePanel savePanel];
|
||||
setupSavePanel(s);
|
||||
// panel is autoreleased
|
||||
return runSavePanel(windowWindow(parent), s);
|
||||
}
|
||||
|
||||
// I would use a completion handler for NSAlert as well, but alas NSAlert's are 10.9 and higher only
|
||||
@interface libuiCodeModalAlertPanel : NSObject {
|
||||
NSAlert *panel;
|
||||
NSWindow *parent;
|
||||
}
|
||||
- (id)initWithPanel:(NSAlert *)p parent:(NSWindow *)w;
|
||||
- (NSInteger)run;
|
||||
- (void)panelEnded:(NSAlert *)panel result:(NSInteger)result data:(void *)data;
|
||||
@end
|
||||
|
||||
@implementation libuiCodeModalAlertPanel
|
||||
|
||||
- (id)initWithPanel:(NSAlert *)p parent:(NSWindow *)w
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self->panel = p;
|
||||
self->parent = w;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSInteger)run
|
||||
{
|
||||
[self->panel beginSheetModalForWindow:self->parent
|
||||
modalDelegate:self
|
||||
didEndSelector:@selector(panelEnded:result:data:)
|
||||
contextInfo:NULL];
|
||||
return [realNSApp() runModalForWindow:[self->panel window]];
|
||||
}
|
||||
|
||||
- (void)panelEnded:(NSAlert *)panel result:(NSInteger)result data:(void *)data
|
||||
{
|
||||
[realNSApp() stopModalWithCode:result];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static void msgbox(NSWindow *parent, const char *title, const char *description, NSAlertStyle style)
|
||||
{
|
||||
NSAlert *a;
|
||||
libuiCodeModalAlertPanel *cm;
|
||||
|
||||
a = [NSAlert new];
|
||||
[a setAlertStyle:style];
|
||||
[a setShowsHelp:NO];
|
||||
[a setShowsSuppressionButton:NO];
|
||||
[a setMessageText:toNSString(title)];
|
||||
[a setInformativeText:toNSString(description)];
|
||||
[a addButtonWithTitle:@"OK"];
|
||||
cm = [[libuiCodeModalAlertPanel alloc] initWithPanel:a parent:parent];
|
||||
[cm run];
|
||||
[cm release];
|
||||
[a release];
|
||||
}
|
||||
|
||||
void uiMsgBox(uiWindow *parent, const char *title, const char *description)
|
||||
{
|
||||
msgbox(windowWindow(parent), title, description, NSInformationalAlertStyle);
|
||||
}
|
||||
|
||||
void uiMsgBoxError(uiWindow *parent, const char *title, const char *description)
|
||||
{
|
||||
msgbox(windowWindow(parent), title, description, NSCriticalAlertStyle);
|
||||
}
|
||||
Vendored
+292
@@ -0,0 +1,292 @@
|
||||
// 15 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// TODO need to jiggle on tab change too (second page disabled tab label initially ambiguous)
|
||||
|
||||
@interface tabPage : NSObject {
|
||||
struct singleChildConstraints constraints;
|
||||
int margined;
|
||||
NSView *view; // the NSTabViewItem view itself
|
||||
NSObject *pageID;
|
||||
}
|
||||
@property uiControl *c;
|
||||
@property NSLayoutPriority oldHorzHuggingPri;
|
||||
@property NSLayoutPriority oldVertHuggingPri;
|
||||
- (id)initWithView:(NSView *)v pageID:(NSObject *)o;
|
||||
- (NSView *)childView;
|
||||
- (void)establishChildConstraints;
|
||||
- (void)removeChildConstraints;
|
||||
- (int)isMargined;
|
||||
- (void)setMargined:(int)m;
|
||||
@end
|
||||
|
||||
struct uiTab {
|
||||
uiDarwinControl c;
|
||||
NSTabView *tabview;
|
||||
NSMutableArray *pages;
|
||||
NSLayoutPriority horzHuggingPri;
|
||||
NSLayoutPriority vertHuggingPri;
|
||||
};
|
||||
|
||||
@implementation tabPage
|
||||
|
||||
- (id)initWithView:(NSView *)v pageID:(NSObject *)o
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
self->view = [v retain];
|
||||
self->pageID = [o retain];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self removeChildConstraints];
|
||||
[self->view release];
|
||||
[self->pageID release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSView *)childView
|
||||
{
|
||||
return (NSView *) uiControlHandle(self.c);
|
||||
}
|
||||
|
||||
- (void)establishChildConstraints
|
||||
{
|
||||
[self removeChildConstraints];
|
||||
if (self.c == NULL)
|
||||
return;
|
||||
singleChildConstraintsEstablish(&(self->constraints),
|
||||
self->view, [self childView],
|
||||
uiDarwinControlHugsTrailingEdge(uiDarwinControl(self.c)),
|
||||
uiDarwinControlHugsBottom(uiDarwinControl(self.c)),
|
||||
self->margined,
|
||||
@"uiTab page");
|
||||
}
|
||||
|
||||
- (void)removeChildConstraints
|
||||
{
|
||||
singleChildConstraintsRemove(&(self->constraints), self->view);
|
||||
}
|
||||
|
||||
- (int)isMargined
|
||||
{
|
||||
return self->margined;
|
||||
}
|
||||
|
||||
- (void)setMargined:(int)m
|
||||
{
|
||||
self->margined = m;
|
||||
singleChildConstraintsSetMargined(&(self->constraints), self->margined);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static void uiTabDestroy(uiControl *c)
|
||||
{
|
||||
uiTab *t = uiTab(c);
|
||||
tabPage *page;
|
||||
|
||||
// first remove all tab pages so we can destroy all the children
|
||||
while ([t->tabview numberOfTabViewItems] != 0)
|
||||
[t->tabview removeTabViewItem:[t->tabview tabViewItemAtIndex:0]];
|
||||
// then destroy all the children
|
||||
for (page in t->pages) {
|
||||
[page removeChildConstraints];
|
||||
uiControlSetParent(page.c, NULL);
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(page.c), nil);
|
||||
uiControlDestroy(page.c);
|
||||
}
|
||||
// and finally destroy ourselves
|
||||
[t->pages release];
|
||||
[t->tabview release];
|
||||
uiFreeControl(uiControl(t));
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultHandle(uiTab, tabview)
|
||||
uiDarwinControlDefaultParent(uiTab, tabview)
|
||||
uiDarwinControlDefaultSetParent(uiTab, tabview)
|
||||
uiDarwinControlDefaultToplevel(uiTab, tabview)
|
||||
uiDarwinControlDefaultVisible(uiTab, tabview)
|
||||
uiDarwinControlDefaultShow(uiTab, tabview)
|
||||
uiDarwinControlDefaultHide(uiTab, tabview)
|
||||
uiDarwinControlDefaultEnabled(uiTab, tabview)
|
||||
uiDarwinControlDefaultEnable(uiTab, tabview)
|
||||
uiDarwinControlDefaultDisable(uiTab, tabview)
|
||||
|
||||
static void uiTabSyncEnableState(uiDarwinControl *c, int enabled)
|
||||
{
|
||||
uiTab *t = uiTab(c);
|
||||
tabPage *page;
|
||||
|
||||
if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(t), enabled))
|
||||
return;
|
||||
for (page in t->pages)
|
||||
uiDarwinControlSyncEnableState(uiDarwinControl(page.c), enabled);
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultSetSuperview(uiTab, tabview)
|
||||
|
||||
static void tabRelayout(uiTab *t)
|
||||
{
|
||||
tabPage *page;
|
||||
|
||||
for (page in t->pages)
|
||||
[page establishChildConstraints];
|
||||
// and this gets rid of some weird issues with regards to box alignment
|
||||
jiggleViewLayout(t->tabview);
|
||||
}
|
||||
|
||||
BOOL uiTabHugsTrailingEdge(uiDarwinControl *c)
|
||||
{
|
||||
uiTab *t = uiTab(c);
|
||||
|
||||
return t->horzHuggingPri < NSLayoutPriorityWindowSizeStayPut;
|
||||
}
|
||||
|
||||
BOOL uiTabHugsBottom(uiDarwinControl *c)
|
||||
{
|
||||
uiTab *t = uiTab(c);
|
||||
|
||||
return t->vertHuggingPri < NSLayoutPriorityWindowSizeStayPut;
|
||||
}
|
||||
|
||||
static void uiTabChildEdgeHuggingChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiTab *t = uiTab(c);
|
||||
|
||||
tabRelayout(t);
|
||||
}
|
||||
|
||||
static NSLayoutPriority uiTabHuggingPriority(uiDarwinControl *c, NSLayoutConstraintOrientation orientation)
|
||||
{
|
||||
uiTab *t = uiTab(c);
|
||||
|
||||
if (orientation == NSLayoutConstraintOrientationHorizontal)
|
||||
return t->horzHuggingPri;
|
||||
return t->vertHuggingPri;
|
||||
}
|
||||
|
||||
static void uiTabSetHuggingPriority(uiDarwinControl *c, NSLayoutPriority priority, NSLayoutConstraintOrientation orientation)
|
||||
{
|
||||
uiTab *t = uiTab(c);
|
||||
|
||||
if (orientation == NSLayoutConstraintOrientationHorizontal)
|
||||
t->horzHuggingPri = priority;
|
||||
else
|
||||
t->vertHuggingPri = priority;
|
||||
uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(t));
|
||||
}
|
||||
|
||||
static void uiTabChildVisibilityChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiTab *t = uiTab(c);
|
||||
|
||||
tabRelayout(t);
|
||||
}
|
||||
|
||||
void uiTabAppend(uiTab *t, const char *name, uiControl *child)
|
||||
{
|
||||
uiTabInsertAt(t, name, [t->pages count], child);
|
||||
}
|
||||
|
||||
void uiTabInsertAt(uiTab *t, const char *name, int n, uiControl *child)
|
||||
{
|
||||
tabPage *page;
|
||||
NSView *view;
|
||||
NSTabViewItem *i;
|
||||
NSObject *pageID;
|
||||
|
||||
uiControlSetParent(child, uiControl(t));
|
||||
|
||||
view = [[[NSView alloc] initWithFrame:NSZeroRect] autorelease];
|
||||
// note: if we turn off the autoresizing mask, nothing shows up
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(child), view);
|
||||
uiDarwinControlSyncEnableState(uiDarwinControl(child), uiControlEnabledToUser(uiControl(t)));
|
||||
|
||||
// the documentation says these can be nil but the headers say these must not be; let's be safe and make them non-nil anyway
|
||||
pageID = [NSObject new];
|
||||
page = [[[tabPage alloc] initWithView:view pageID:pageID] autorelease];
|
||||
page.c = child;
|
||||
|
||||
// don't hug, just in case we're a stretchy tab
|
||||
page.oldHorzHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(page.c), NSLayoutConstraintOrientationHorizontal);
|
||||
page.oldVertHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(page.c), NSLayoutConstraintOrientationVertical);
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(page.c), NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationHorizontal);
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(page.c), NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationVertical);
|
||||
|
||||
[t->pages insertObject:page atIndex:n];
|
||||
|
||||
i = [[[NSTabViewItem alloc] initWithIdentifier:pageID] autorelease];
|
||||
[i setLabel:toNSString(name)];
|
||||
[i setView:view];
|
||||
[t->tabview insertTabViewItem:i atIndex:n];
|
||||
|
||||
tabRelayout(t);
|
||||
}
|
||||
|
||||
void uiTabDelete(uiTab *t, int n)
|
||||
{
|
||||
tabPage *page;
|
||||
uiControl *child;
|
||||
NSTabViewItem *i;
|
||||
|
||||
page = (tabPage *) [t->pages objectAtIndex:n];
|
||||
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(page.c), page.oldHorzHuggingPri, NSLayoutConstraintOrientationHorizontal);
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(page.c), page.oldVertHuggingPri, NSLayoutConstraintOrientationVertical);
|
||||
|
||||
child = page.c;
|
||||
[page removeChildConstraints];
|
||||
[t->pages removeObjectAtIndex:n];
|
||||
|
||||
uiControlSetParent(child, NULL);
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(child), nil);
|
||||
|
||||
i = [t->tabview tabViewItemAtIndex:n];
|
||||
[t->tabview removeTabViewItem:i];
|
||||
|
||||
tabRelayout(t);
|
||||
}
|
||||
|
||||
int uiTabNumPages(uiTab *t)
|
||||
{
|
||||
return [t->pages count];
|
||||
}
|
||||
|
||||
int uiTabMargined(uiTab *t, int n)
|
||||
{
|
||||
tabPage *page;
|
||||
|
||||
page = (tabPage *) [t->pages objectAtIndex:n];
|
||||
return [page isMargined];
|
||||
}
|
||||
|
||||
void uiTabSetMargined(uiTab *t, int n, int margined)
|
||||
{
|
||||
tabPage *page;
|
||||
|
||||
page = (tabPage *) [t->pages objectAtIndex:n];
|
||||
[page setMargined:margined];
|
||||
}
|
||||
|
||||
uiTab *uiNewTab(void)
|
||||
{
|
||||
uiTab *t;
|
||||
|
||||
uiDarwinNewControl(uiTab, t);
|
||||
|
||||
t->tabview = [[NSTabView alloc] initWithFrame:NSZeroRect];
|
||||
// also good for NSTabView (same selector and everything)
|
||||
uiDarwinSetControlFont((NSControl *) (t->tabview), NSRegularControlSize);
|
||||
|
||||
t->pages = [NSMutableArray new];
|
||||
|
||||
// default to low hugging to not hug edges
|
||||
t->horzHuggingPri = NSLayoutPriorityDefaultLow;
|
||||
t->vertHuggingPri = NSLayoutPriorityDefaultLow;
|
||||
|
||||
return t;
|
||||
}
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
// 10 april 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
char *uiDarwinNSStringToText(NSString *s)
|
||||
{
|
||||
char *out;
|
||||
|
||||
out = strdup([s UTF8String]);
|
||||
if (out == NULL) {
|
||||
fprintf(stderr, "memory exhausted in uiDarwinNSStringToText()\n");
|
||||
abort();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void uiFreeText(char *s)
|
||||
{
|
||||
free(s);
|
||||
}
|
||||
Vendored
+146
@@ -0,0 +1,146 @@
|
||||
// 6 january 2015
|
||||
#define MAC_OS_X_VERSION_MIN_REQUIRED MAC_OS_X_VERSION_10_8
|
||||
#define MAC_OS_X_VERSION_MAX_ALLOWED MAC_OS_X_VERSION_10_8
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "../ui.h"
|
||||
#import "../ui_darwin.h"
|
||||
#import "../common/uipriv.h"
|
||||
|
||||
#if __has_feature(objc_arc)
|
||||
#error Sorry, libui cannot be compiled with ARC.
|
||||
#endif
|
||||
|
||||
#define toNSString(str) [NSString stringWithUTF8String:(str)]
|
||||
#define fromNSString(str) [(str) UTF8String]
|
||||
|
||||
#ifndef NSAppKitVersionNumber10_9
|
||||
#define NSAppKitVersionNumber10_9 1265
|
||||
#endif
|
||||
|
||||
/*TODO remove this*/typedef struct uiImage uiImage;
|
||||
|
||||
// menu.m
|
||||
@interface menuManager : NSObject {
|
||||
struct mapTable *items;
|
||||
BOOL hasQuit;
|
||||
BOOL hasPreferences;
|
||||
BOOL hasAbout;
|
||||
}
|
||||
@property (strong) NSMenuItem *quitItem;
|
||||
@property (strong) NSMenuItem *preferencesItem;
|
||||
@property (strong) NSMenuItem *aboutItem;
|
||||
// NSMenuValidation is only informal
|
||||
- (BOOL)validateMenuItem:(NSMenuItem *)item;
|
||||
- (NSMenu *)makeMenubar;
|
||||
@end
|
||||
extern void finalizeMenus(void);
|
||||
extern void uninitMenus(void);
|
||||
|
||||
// main.m
|
||||
@interface applicationClass : NSApplication
|
||||
@end
|
||||
// this is needed because NSApp is of type id, confusing clang
|
||||
#define realNSApp() ((applicationClass *) NSApp)
|
||||
@interface appDelegate : NSObject <NSApplicationDelegate>
|
||||
@property (strong) menuManager *menuManager;
|
||||
@end
|
||||
#define appDelegate() ((appDelegate *) [realNSApp() delegate])
|
||||
struct nextEventArgs {
|
||||
NSEventMask mask;
|
||||
NSDate *duration;
|
||||
// LONGTERM no NSRunLoopMode?
|
||||
NSString *mode;
|
||||
BOOL dequeue;
|
||||
};
|
||||
extern int mainStep(struct nextEventArgs *nea, BOOL (^interceptEvent)(NSEvent *));
|
||||
|
||||
// util.m
|
||||
extern void disableAutocorrect(NSTextView *);
|
||||
|
||||
// entry.m
|
||||
extern void finishNewTextField(NSTextField *, BOOL);
|
||||
extern NSTextField *newEditableTextField(void);
|
||||
|
||||
// window.m
|
||||
@interface libuiNSWindow : NSWindow
|
||||
- (void)libui_doMove:(NSEvent *)initialEvent;
|
||||
- (void)libui_doResize:(NSEvent *)initialEvent on:(uiWindowResizeEdge)edge;
|
||||
@end
|
||||
extern uiWindow *windowFromNSWindow(NSWindow *);
|
||||
|
||||
// alloc.m
|
||||
extern NSMutableArray *delegates;
|
||||
extern void initAlloc(void);
|
||||
extern void uninitAlloc(void);
|
||||
|
||||
// autolayout.m
|
||||
extern NSLayoutConstraint *mkConstraint(id view1, NSLayoutAttribute attr1, NSLayoutRelation relation, id view2, NSLayoutAttribute attr2, CGFloat multiplier, CGFloat c, NSString *desc);
|
||||
extern void jiggleViewLayout(NSView *view);
|
||||
struct singleChildConstraints {
|
||||
NSLayoutConstraint *leadingConstraint;
|
||||
NSLayoutConstraint *topConstraint;
|
||||
NSLayoutConstraint *trailingConstraintGreater;
|
||||
NSLayoutConstraint *trailingConstraintEqual;
|
||||
NSLayoutConstraint *bottomConstraintGreater;
|
||||
NSLayoutConstraint *bottomConstraintEqual;
|
||||
};
|
||||
extern void singleChildConstraintsEstablish(struct singleChildConstraints *c, NSView *contentView, NSView *childView, BOOL hugsTrailing, BOOL hugsBottom, int margined, NSString *desc);
|
||||
extern void singleChildConstraintsRemove(struct singleChildConstraints *c, NSView *cv);
|
||||
extern void singleChildConstraintsSetMargined(struct singleChildConstraints *c, int margined);
|
||||
|
||||
// map.m
|
||||
extern struct mapTable *newMap(void);
|
||||
extern void mapDestroy(struct mapTable *m);
|
||||
extern void *mapGet(struct mapTable *m, void *key);
|
||||
extern void mapSet(struct mapTable *m, void *key, void *value);
|
||||
extern void mapDelete(struct mapTable *m, void *key);
|
||||
extern void mapWalk(struct mapTable *m, void (*f)(void *key, void *value));
|
||||
extern void mapReset(struct mapTable *m);
|
||||
|
||||
// area.m
|
||||
extern int sendAreaEvents(NSEvent *);
|
||||
|
||||
// areaevents.m
|
||||
extern BOOL fromKeycode(unsigned short keycode, uiAreaKeyEvent *ke);
|
||||
extern BOOL keycodeModifier(unsigned short keycode, uiModifiers *mod);
|
||||
|
||||
// draw.m
|
||||
extern uiDrawContext *newContext(CGContextRef, CGFloat);
|
||||
extern void freeContext(uiDrawContext *);
|
||||
|
||||
// drawtext.m
|
||||
extern uiDrawTextFont *mkTextFont(CTFontRef f, BOOL retain);
|
||||
extern uiDrawTextFont *mkTextFontFromNSFont(NSFont *f);
|
||||
extern void doDrawText(CGContextRef c, CGFloat cheight, double x, double y, uiDrawTextLayout *layout);
|
||||
|
||||
// fontbutton.m
|
||||
extern BOOL fontButtonInhibitSendAction(SEL sel, id from, id to);
|
||||
extern BOOL fontButtonOverrideTargetForAction(SEL sel, id from, id to, id *override);
|
||||
extern void setupFontPanel(void);
|
||||
|
||||
// colorbutton.m
|
||||
extern BOOL colorButtonInhibitSendAction(SEL sel, id from, id to);
|
||||
|
||||
// scrollview.m
|
||||
struct scrollViewCreateParams {
|
||||
NSView *DocumentView;
|
||||
NSColor *BackgroundColor;
|
||||
BOOL DrawsBackground;
|
||||
BOOL Bordered;
|
||||
BOOL HScroll;
|
||||
BOOL VScroll;
|
||||
};
|
||||
struct scrollViewData;
|
||||
extern NSScrollView *mkScrollView(struct scrollViewCreateParams *p, struct scrollViewData **dout);
|
||||
extern void scrollViewSetScrolling(NSScrollView *sv, struct scrollViewData *d, BOOL hscroll, BOOL vscroll);
|
||||
extern void scrollViewFreeData(NSScrollView *sv, struct scrollViewData *d);
|
||||
|
||||
// label.m
|
||||
extern NSTextField *newLabel(NSString *str);
|
||||
|
||||
// image.m
|
||||
extern NSImage *imageImage(uiImage *);
|
||||
|
||||
// winmoveresize.m
|
||||
extern void doManualMove(NSWindow *w, NSEvent *initialEvent);
|
||||
extern void doManualResize(NSWindow *w, NSEvent *initialEvent, uiWindowResizeEdge edge);
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
// 7 april 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// LONGTERM do we really want to do this? make it an option?
|
||||
void disableAutocorrect(NSTextView *tv)
|
||||
{
|
||||
[tv setEnabledTextCheckingTypes:0];
|
||||
[tv setAutomaticDashSubstitutionEnabled:NO];
|
||||
// don't worry about automatic data detection; it won't change stringValue (thanks pretty_function in irc.freenode.net/#macdev)
|
||||
[tv setAutomaticSpellingCorrectionEnabled:NO];
|
||||
[tv setAutomaticTextReplacementEnabled:NO];
|
||||
[tv setAutomaticQuoteSubstitutionEnabled:NO];
|
||||
[tv setAutomaticLinkDetectionEnabled:NO];
|
||||
[tv setSmartInsertDeleteEnabled:NO];
|
||||
}
|
||||
Vendored
+407
@@ -0,0 +1,407 @@
|
||||
// 15 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
#define defaultStyleMask (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)
|
||||
|
||||
struct uiWindow {
|
||||
uiDarwinControl c;
|
||||
NSWindow *window;
|
||||
uiControl *child;
|
||||
int margined;
|
||||
int (*onClosing)(uiWindow *, void *);
|
||||
void *onClosingData;
|
||||
struct singleChildConstraints constraints;
|
||||
void (*onContentSizeChanged)(uiWindow *, void *);
|
||||
void *onContentSizeChangedData;
|
||||
BOOL suppressSizeChanged;
|
||||
int fullscreen;
|
||||
int borderless;
|
||||
};
|
||||
|
||||
@implementation libuiNSWindow
|
||||
|
||||
- (void)libui_doMove:(NSEvent *)initialEvent
|
||||
{
|
||||
doManualMove(self, initialEvent);
|
||||
}
|
||||
|
||||
- (void)libui_doResize:(NSEvent *)initialEvent on:(uiWindowResizeEdge)edge
|
||||
{
|
||||
doManualResize(self, initialEvent, edge);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface windowDelegateClass : NSObject<NSWindowDelegate> {
|
||||
struct mapTable *windows;
|
||||
}
|
||||
- (BOOL)windowShouldClose:(id)sender;
|
||||
- (void)windowDidResize:(NSNotification *)note;
|
||||
- (void)windowDidEnterFullScreen:(NSNotification *)note;
|
||||
- (void)windowDidExitFullScreen:(NSNotification *)note;
|
||||
- (void)registerWindow:(uiWindow *)w;
|
||||
- (void)unregisterWindow:(uiWindow *)w;
|
||||
- (uiWindow *)lookupWindow:(NSWindow *)w;
|
||||
@end
|
||||
|
||||
@implementation windowDelegateClass
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
self->windows = newMap();
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
mapDestroy(self->windows);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (BOOL)windowShouldClose:(id)sender
|
||||
{
|
||||
uiWindow *w;
|
||||
|
||||
w = [self lookupWindow:((NSWindow *) sender)];
|
||||
// w should not be NULL; we are only the delegate of registered windows
|
||||
if ((*(w->onClosing))(w, w->onClosingData))
|
||||
uiControlDestroy(uiControl(w));
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)windowDidResize:(NSNotification *)note
|
||||
{
|
||||
uiWindow *w;
|
||||
|
||||
w = [self lookupWindow:((NSWindow *) [note object])];
|
||||
if (!w->suppressSizeChanged)
|
||||
(*(w->onContentSizeChanged))(w, w->onContentSizeChangedData);
|
||||
}
|
||||
|
||||
- (void)windowDidEnterFullScreen:(NSNotification *)note
|
||||
{
|
||||
uiWindow *w;
|
||||
|
||||
w = [self lookupWindow:((NSWindow *) [note object])];
|
||||
if (!w->suppressSizeChanged)
|
||||
w->fullscreen = 1;
|
||||
}
|
||||
|
||||
- (void)windowDidExitFullScreen:(NSNotification *)note
|
||||
{
|
||||
uiWindow *w;
|
||||
|
||||
w = [self lookupWindow:((NSWindow *) [note object])];
|
||||
if (!w->suppressSizeChanged)
|
||||
w->fullscreen = 0;
|
||||
}
|
||||
|
||||
- (void)registerWindow:(uiWindow *)w
|
||||
{
|
||||
mapSet(self->windows, w->window, w);
|
||||
[w->window setDelegate:self];
|
||||
}
|
||||
|
||||
- (void)unregisterWindow:(uiWindow *)w
|
||||
{
|
||||
[w->window setDelegate:nil];
|
||||
mapDelete(self->windows, w->window);
|
||||
}
|
||||
|
||||
- (uiWindow *)lookupWindow:(NSWindow *)w
|
||||
{
|
||||
uiWindow *v;
|
||||
|
||||
v = uiWindow(mapGet(self->windows, w));
|
||||
// this CAN (and IS ALLOWED TO) return NULL, just in case we're called with some OS X-provided window as the key window
|
||||
return v;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static windowDelegateClass *windowDelegate = nil;
|
||||
|
||||
static void removeConstraints(uiWindow *w)
|
||||
{
|
||||
NSView *cv;
|
||||
|
||||
cv = [w->window contentView];
|
||||
singleChildConstraintsRemove(&(w->constraints), cv);
|
||||
}
|
||||
|
||||
static void uiWindowDestroy(uiControl *c)
|
||||
{
|
||||
uiWindow *w = uiWindow(c);
|
||||
|
||||
// hide the window
|
||||
[w->window orderOut:w->window];
|
||||
removeConstraints(w);
|
||||
if (w->child != NULL) {
|
||||
uiControlSetParent(w->child, NULL);
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(w->child), nil);
|
||||
uiControlDestroy(w->child);
|
||||
}
|
||||
[windowDelegate unregisterWindow:w];
|
||||
[w->window release];
|
||||
uiFreeControl(uiControl(w));
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultHandle(uiWindow, window)
|
||||
|
||||
uiControl *uiWindowParent(uiControl *c)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void uiWindowSetParent(uiControl *c, uiControl *parent)
|
||||
{
|
||||
uiUserBugCannotSetParentOnToplevel("uiWindow");
|
||||
}
|
||||
|
||||
static int uiWindowToplevel(uiControl *c)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int uiWindowVisible(uiControl *c)
|
||||
{
|
||||
uiWindow *w = uiWindow(c);
|
||||
|
||||
return [w->window isVisible];
|
||||
}
|
||||
|
||||
static void uiWindowShow(uiControl *c)
|
||||
{
|
||||
uiWindow *w = (uiWindow *) c;
|
||||
|
||||
[w->window makeKeyAndOrderFront:w->window];
|
||||
}
|
||||
|
||||
static void uiWindowHide(uiControl *c)
|
||||
{
|
||||
uiWindow *w = (uiWindow *) c;
|
||||
|
||||
[w->window orderOut:w->window];
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultEnabled(uiWindow, window)
|
||||
uiDarwinControlDefaultEnable(uiWindow, window)
|
||||
uiDarwinControlDefaultDisable(uiWindow, window)
|
||||
|
||||
static void uiWindowSyncEnableState(uiDarwinControl *c, int enabled)
|
||||
{
|
||||
uiWindow *w = uiWindow(c);
|
||||
|
||||
if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(w), enabled))
|
||||
return;
|
||||
if (w->child != NULL)
|
||||
uiDarwinControlSyncEnableState(uiDarwinControl(w->child), enabled);
|
||||
}
|
||||
|
||||
static void uiWindowSetSuperview(uiDarwinControl *c, NSView *superview)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
static void windowRelayout(uiWindow *w)
|
||||
{
|
||||
NSView *childView;
|
||||
NSView *contentView;
|
||||
|
||||
removeConstraints(w);
|
||||
if (w->child == NULL)
|
||||
return;
|
||||
childView = (NSView *) uiControlHandle(w->child);
|
||||
contentView = [w->window contentView];
|
||||
singleChildConstraintsEstablish(&(w->constraints),
|
||||
contentView, childView,
|
||||
uiDarwinControlHugsTrailingEdge(uiDarwinControl(w->child)),
|
||||
uiDarwinControlHugsBottom(uiDarwinControl(w->child)),
|
||||
w->margined,
|
||||
@"uiWindow");
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultHugsTrailingEdge(uiWindow, window)
|
||||
uiDarwinControlDefaultHugsBottom(uiWindow, window)
|
||||
|
||||
static void uiWindowChildEdgeHuggingChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiWindow *w = uiWindow(c);
|
||||
|
||||
windowRelayout(w);
|
||||
}
|
||||
|
||||
// TODO
|
||||
uiDarwinControlDefaultHuggingPriority(uiWindow, window)
|
||||
uiDarwinControlDefaultSetHuggingPriority(uiWindow, window)
|
||||
// end TODO
|
||||
|
||||
static void uiWindowChildVisibilityChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiWindow *w = uiWindow(c);
|
||||
|
||||
windowRelayout(w);
|
||||
}
|
||||
|
||||
char *uiWindowTitle(uiWindow *w)
|
||||
{
|
||||
return uiDarwinNSStringToText([w->window title]);
|
||||
}
|
||||
|
||||
void uiWindowSetTitle(uiWindow *w, const char *title)
|
||||
{
|
||||
[w->window setTitle:toNSString(title)];
|
||||
}
|
||||
|
||||
void uiWindowContentSize(uiWindow *w, int *width, int *height)
|
||||
{
|
||||
NSRect r;
|
||||
|
||||
r = [w->window contentRectForFrameRect:[w->window frame]];
|
||||
*width = r.size.width;
|
||||
*height = r.size.height;
|
||||
}
|
||||
|
||||
void uiWindowSetContentSize(uiWindow *w, int width, int height)
|
||||
{
|
||||
w->suppressSizeChanged = YES;
|
||||
[w->window setContentSize:NSMakeSize(width, height)];
|
||||
w->suppressSizeChanged = NO;
|
||||
}
|
||||
|
||||
int uiWindowFullscreen(uiWindow *w)
|
||||
{
|
||||
return w->fullscreen;
|
||||
}
|
||||
|
||||
void uiWindowSetFullscreen(uiWindow *w, int fullscreen)
|
||||
{
|
||||
if (w->fullscreen && fullscreen)
|
||||
return;
|
||||
if (!w->fullscreen && !fullscreen)
|
||||
return;
|
||||
w->fullscreen = fullscreen;
|
||||
if (w->fullscreen && w->borderless) // borderless doesn't play nice with fullscreen; don't toggle while borderless
|
||||
return;
|
||||
w->suppressSizeChanged = YES;
|
||||
[w->window toggleFullScreen:w->window];
|
||||
w->suppressSizeChanged = NO;
|
||||
if (!w->fullscreen && w->borderless) // borderless doesn't play nice with fullscreen; restore borderless after removing
|
||||
[w->window setStyleMask:NSBorderlessWindowMask];
|
||||
}
|
||||
|
||||
void uiWindowOnContentSizeChanged(uiWindow *w, void (*f)(uiWindow *, void *), void *data)
|
||||
{
|
||||
w->onContentSizeChanged = f;
|
||||
w->onContentSizeChangedData = data;
|
||||
}
|
||||
|
||||
void uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *, void *), void *data)
|
||||
{
|
||||
w->onClosing = f;
|
||||
w->onClosingData = data;
|
||||
}
|
||||
|
||||
int uiWindowBorderless(uiWindow *w)
|
||||
{
|
||||
return w->borderless;
|
||||
}
|
||||
|
||||
void uiWindowSetBorderless(uiWindow *w, int borderless)
|
||||
{
|
||||
w->borderless = borderless;
|
||||
if (w->borderless) {
|
||||
// borderless doesn't play nice with fullscreen; wait for later
|
||||
if (!w->fullscreen)
|
||||
[w->window setStyleMask:NSBorderlessWindowMask];
|
||||
} else {
|
||||
[w->window setStyleMask:defaultStyleMask];
|
||||
// borderless doesn't play nice with fullscreen; restore state
|
||||
if (w->fullscreen) {
|
||||
w->suppressSizeChanged = YES;
|
||||
[w->window toggleFullScreen:w->window];
|
||||
w->suppressSizeChanged = NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void uiWindowSetChild(uiWindow *w, uiControl *child)
|
||||
{
|
||||
NSView *childView;
|
||||
|
||||
if (w->child != NULL) {
|
||||
childView = (NSView *) uiControlHandle(w->child);
|
||||
[childView removeFromSuperview];
|
||||
uiControlSetParent(w->child, NULL);
|
||||
}
|
||||
w->child = child;
|
||||
if (w->child != NULL) {
|
||||
uiControlSetParent(w->child, uiControl(w));
|
||||
childView = (NSView *) uiControlHandle(w->child);
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(w->child), [w->window contentView]);
|
||||
uiDarwinControlSyncEnableState(uiDarwinControl(w->child), uiControlEnabledToUser(uiControl(w)));
|
||||
}
|
||||
windowRelayout(w);
|
||||
}
|
||||
|
||||
int uiWindowMargined(uiWindow *w)
|
||||
{
|
||||
return w->margined;
|
||||
}
|
||||
|
||||
void uiWindowSetMargined(uiWindow *w, int margined)
|
||||
{
|
||||
w->margined = margined;
|
||||
singleChildConstraintsSetMargined(&(w->constraints), w->margined);
|
||||
}
|
||||
|
||||
static int defaultOnClosing(uiWindow *w, void *data)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void defaultOnPositionContentSizeChanged(uiWindow *w, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
uiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar)
|
||||
{
|
||||
uiWindow *w;
|
||||
|
||||
finalizeMenus();
|
||||
|
||||
uiDarwinNewControl(uiWindow, w);
|
||||
|
||||
w->window = [[libuiNSWindow alloc] initWithContentRect:NSMakeRect(0, 0, (CGFloat) width, (CGFloat) height)
|
||||
styleMask:defaultStyleMask
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:YES];
|
||||
[w->window setTitle:toNSString(title)];
|
||||
|
||||
// do NOT release when closed
|
||||
// we manually do this in uiWindowDestroy() above
|
||||
[w->window setReleasedWhenClosed:NO];
|
||||
|
||||
if (windowDelegate == nil) {
|
||||
windowDelegate = [[windowDelegateClass new] autorelease];
|
||||
[delegates addObject:windowDelegate];
|
||||
}
|
||||
[windowDelegate registerWindow:w];
|
||||
uiWindowOnClosing(w, defaultOnClosing, NULL);
|
||||
uiWindowOnContentSizeChanged(w, defaultOnPositionContentSizeChanged, NULL);
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
// utility function for menus
|
||||
uiWindow *windowFromNSWindow(NSWindow *w)
|
||||
{
|
||||
if (w == nil)
|
||||
return NULL;
|
||||
if (windowDelegate == nil) // no windows were created yet; we're called with some OS X-provided window
|
||||
return NULL;
|
||||
return [windowDelegate lookupWindow:w];
|
||||
}
|
||||
Vendored
+253
@@ -0,0 +1,253 @@
|
||||
// 1 november 2016
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// because we are changing the window frame each time the mouse moves, the successive -[NSEvent locationInWindow]s cannot be meaningfully used together
|
||||
// make sure they are all following some sort of standard to avoid this problem; the screen is the most obvious possibility since it requires only one conversion (the only one that a NSWindow provides)
|
||||
static NSPoint makeIndependent(NSPoint p, NSWindow *w)
|
||||
{
|
||||
NSRect r;
|
||||
|
||||
r.origin = p;
|
||||
// mikeash in irc.freenode.net/#macdev confirms both that any size will do and that we can safely ignore the resultant size
|
||||
r.size = NSZeroSize;
|
||||
return [w convertRectToScreen:r].origin;
|
||||
}
|
||||
|
||||
struct onMoveDragParams {
|
||||
NSWindow *w;
|
||||
// using the previous point causes weird issues like the mouse seeming to fall behind the window edge... so do this instead
|
||||
// TODO will this make things like the menubar and dock easier too?
|
||||
NSRect initialFrame;
|
||||
NSPoint initialPoint;
|
||||
};
|
||||
|
||||
void onMoveDrag(struct onMoveDragParams *p, NSEvent *e)
|
||||
{
|
||||
NSPoint new;
|
||||
NSRect frame;
|
||||
CGFloat offx, offy;
|
||||
|
||||
new = makeIndependent([e locationInWindow], p->w);
|
||||
frame = p->initialFrame;
|
||||
|
||||
offx = new.x - p->initialPoint.x;
|
||||
offy = new.y - p->initialPoint.y;
|
||||
frame.origin.x += offx;
|
||||
frame.origin.y += offy;
|
||||
|
||||
// TODO handle the menubar
|
||||
// TODO wait the system does this for us already?!
|
||||
|
||||
[p->w setFrameOrigin:frame.origin];
|
||||
}
|
||||
|
||||
void doManualMove(NSWindow *w, NSEvent *initialEvent)
|
||||
{
|
||||
__block struct onMoveDragParams mdp;
|
||||
struct nextEventArgs nea;
|
||||
BOOL (^handleEvent)(NSEvent *e);
|
||||
__block BOOL done;
|
||||
|
||||
// this is only available on 10.11 and newer (LONGTERM FUTURE)
|
||||
// but use it if available; this lets us use the real OS dragging code, which means we can take advantage of OS features like Spaces
|
||||
if ([w respondsToSelector:@selector(performWindowDragWithEvent:)]) {
|
||||
[((id) w) performWindowDragWithEvent:initialEvent];
|
||||
return;
|
||||
}
|
||||
|
||||
mdp.w = w;
|
||||
mdp.initialFrame = [mdp.w frame];
|
||||
mdp.initialPoint = makeIndependent([initialEvent locationInWindow], mdp.w);
|
||||
|
||||
nea.mask = NSLeftMouseDraggedMask | NSLeftMouseUpMask;
|
||||
nea.duration = [NSDate distantFuture];
|
||||
nea.mode = NSEventTrackingRunLoopMode; // nextEventMatchingMask: docs suggest using this for manual mouse tracking
|
||||
nea.dequeue = YES;
|
||||
handleEvent = ^(NSEvent *e) {
|
||||
if ([e type] == NSLeftMouseUp) {
|
||||
done = YES;
|
||||
return YES; // do not send
|
||||
}
|
||||
onMoveDrag(&mdp, e);
|
||||
return YES; // do not send
|
||||
};
|
||||
done = NO;
|
||||
while (mainStep(&nea, handleEvent))
|
||||
if (done)
|
||||
break;
|
||||
}
|
||||
|
||||
// see http://stackoverflow.com/a/40352996/3408572
|
||||
static void minMaxAutoLayoutSizes(NSWindow *w, NSSize *min, NSSize *max)
|
||||
{
|
||||
NSLayoutConstraint *cw, *ch;
|
||||
NSView *contentView;
|
||||
NSRect prevFrame;
|
||||
|
||||
// if adding these constraints causes the window to change size somehow, don't show it to the user and change it back afterwards
|
||||
NSDisableScreenUpdates();
|
||||
prevFrame = [w frame];
|
||||
|
||||
// minimum: encourage the window to be as small as possible
|
||||
contentView = [w contentView];
|
||||
cw = mkConstraint(contentView, NSLayoutAttributeWidth,
|
||||
NSLayoutRelationEqual,
|
||||
nil, NSLayoutAttributeNotAnAttribute,
|
||||
0, 0,
|
||||
@"window minimum width finding constraint");
|
||||
[cw setPriority:NSLayoutPriorityDragThatCanResizeWindow];
|
||||
[contentView addConstraint:cw];
|
||||
ch = mkConstraint(contentView, NSLayoutAttributeHeight,
|
||||
NSLayoutRelationEqual,
|
||||
nil, NSLayoutAttributeNotAnAttribute,
|
||||
0, 0,
|
||||
@"window minimum height finding constraint");
|
||||
[ch setPriority:NSLayoutPriorityDragThatCanResizeWindow];
|
||||
[contentView addConstraint:ch];
|
||||
*min = [contentView fittingSize];
|
||||
[contentView removeConstraint:cw];
|
||||
[contentView removeConstraint:ch];
|
||||
|
||||
// maximum: encourage the window to be as large as possible
|
||||
contentView = [w contentView];
|
||||
cw = mkConstraint(contentView, NSLayoutAttributeWidth,
|
||||
NSLayoutRelationEqual,
|
||||
nil, NSLayoutAttributeNotAnAttribute,
|
||||
0, CGFLOAT_MAX,
|
||||
@"window maximum width finding constraint");
|
||||
[cw setPriority:NSLayoutPriorityDragThatCanResizeWindow];
|
||||
[contentView addConstraint:cw];
|
||||
ch = mkConstraint(contentView, NSLayoutAttributeHeight,
|
||||
NSLayoutRelationEqual,
|
||||
nil, NSLayoutAttributeNotAnAttribute,
|
||||
0, CGFLOAT_MAX,
|
||||
@"window maximum height finding constraint");
|
||||
[ch setPriority:NSLayoutPriorityDragThatCanResizeWindow];
|
||||
[contentView addConstraint:ch];
|
||||
*max = [contentView fittingSize];
|
||||
[contentView removeConstraint:cw];
|
||||
[contentView removeConstraint:ch];
|
||||
|
||||
[w setFrame:prevFrame display:YES]; // TODO really YES?
|
||||
NSEnableScreenUpdates();
|
||||
}
|
||||
|
||||
static void handleResizeLeft(NSRect *frame, NSPoint old, NSPoint new)
|
||||
{
|
||||
frame->origin.x += new.x - old.x;
|
||||
frame->size.width -= new.x - old.x;
|
||||
}
|
||||
|
||||
// TODO properly handle the menubar
|
||||
// TODO wait, OS X does it for us?!
|
||||
static void handleResizeTop(NSRect *frame, NSPoint old, NSPoint new)
|
||||
{
|
||||
frame->size.height += new.y - old.y;
|
||||
}
|
||||
|
||||
static void handleResizeRight(NSRect *frame, NSPoint old, NSPoint new)
|
||||
{
|
||||
frame->size.width += new.x - old.x;
|
||||
}
|
||||
|
||||
|
||||
// TODO properly handle the menubar
|
||||
static void handleResizeBottom(NSRect *frame, NSPoint old, NSPoint new)
|
||||
{
|
||||
frame->origin.y += new.y - old.y;
|
||||
frame->size.height -= new.y - old.y;
|
||||
}
|
||||
|
||||
struct onResizeDragParams {
|
||||
NSWindow *w;
|
||||
// using the previous point causes weird issues like the mouse seeming to fall behind the window edge... so do this instead
|
||||
// TODO will this make things like the menubar and dock easier too?
|
||||
NSRect initialFrame;
|
||||
NSPoint initialPoint;
|
||||
uiWindowResizeEdge edge;
|
||||
NSSize min;
|
||||
NSSize max;
|
||||
};
|
||||
|
||||
static void onResizeDrag(struct onResizeDragParams *p, NSEvent *e)
|
||||
{
|
||||
NSPoint new;
|
||||
NSRect frame;
|
||||
|
||||
new = makeIndependent([e locationInWindow], p->w);
|
||||
frame = p->initialFrame;
|
||||
|
||||
// horizontal
|
||||
switch (p->edge) {
|
||||
case uiWindowResizeEdgeLeft:
|
||||
case uiWindowResizeEdgeTopLeft:
|
||||
case uiWindowResizeEdgeBottomLeft:
|
||||
handleResizeLeft(&frame, p->initialPoint, new);
|
||||
break;
|
||||
case uiWindowResizeEdgeRight:
|
||||
case uiWindowResizeEdgeTopRight:
|
||||
case uiWindowResizeEdgeBottomRight:
|
||||
handleResizeRight(&frame, p->initialPoint, new);
|
||||
break;
|
||||
}
|
||||
// vertical
|
||||
switch (p->edge) {
|
||||
case uiWindowResizeEdgeTop:
|
||||
case uiWindowResizeEdgeTopLeft:
|
||||
case uiWindowResizeEdgeTopRight:
|
||||
handleResizeTop(&frame, p->initialPoint, new);
|
||||
break;
|
||||
case uiWindowResizeEdgeBottom:
|
||||
case uiWindowResizeEdgeBottomLeft:
|
||||
case uiWindowResizeEdgeBottomRight:
|
||||
handleResizeBottom(&frame, p->initialPoint, new);
|
||||
break;
|
||||
}
|
||||
|
||||
// constrain
|
||||
// TODO should we constrain against anything else as well? minMaxAutoLayoutSizes() already gives us nonnegative sizes, but...
|
||||
if (frame.size.width < p->min.width)
|
||||
frame.size.width = p->min.width;
|
||||
if (frame.size.height < p->min.height)
|
||||
frame.size.height = p->min.height;
|
||||
// TODO > or >= ?
|
||||
if (frame.size.width > p->max.width)
|
||||
frame.size.width = p->max.width;
|
||||
if (frame.size.height > p->max.height)
|
||||
frame.size.height = p->max.height;
|
||||
|
||||
[p->w setFrame:frame display:YES]; // and do reflect the new frame immediately
|
||||
}
|
||||
|
||||
// TODO do our events get fired with this? *should* they?
|
||||
void doManualResize(NSWindow *w, NSEvent *initialEvent, uiWindowResizeEdge edge)
|
||||
{
|
||||
__block struct onResizeDragParams rdp;
|
||||
struct nextEventArgs nea;
|
||||
BOOL (^handleEvent)(NSEvent *e);
|
||||
__block BOOL done;
|
||||
|
||||
rdp.w = w;
|
||||
rdp.initialFrame = [rdp.w frame];
|
||||
rdp.initialPoint = makeIndependent([initialEvent locationInWindow], rdp.w);
|
||||
rdp.edge = edge;
|
||||
// TODO what happens if these change during the loop?
|
||||
minMaxAutoLayoutSizes(rdp.w, &(rdp.min), &(rdp.max));
|
||||
|
||||
nea.mask = NSLeftMouseDraggedMask | NSLeftMouseUpMask;
|
||||
nea.duration = [NSDate distantFuture];
|
||||
nea.mode = NSEventTrackingRunLoopMode; // nextEventMatchingMask: docs suggest using this for manual mouse tracking
|
||||
nea.dequeue = YES;
|
||||
handleEvent = ^(NSEvent *e) {
|
||||
if ([e type] == NSLeftMouseUp) {
|
||||
done = YES;
|
||||
return YES; // do not send
|
||||
}
|
||||
onResizeDrag(&rdp, e);
|
||||
return YES; // do not send
|
||||
};
|
||||
done = NO;
|
||||
while (mainStep(&nea, handleEvent))
|
||||
if (done)
|
||||
break;
|
||||
}
|
||||
Vendored
+85
@@ -0,0 +1,85 @@
|
||||
/* 7 april 2015 */
|
||||
#include <string.h>
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
static GPtrArray *allocations;
|
||||
|
||||
#define UINT8(p) ((uint8_t *) (p))
|
||||
#define PVOID(p) ((void *) (p))
|
||||
#define EXTRA (sizeof (size_t) + sizeof (const char **))
|
||||
#define DATA(p) PVOID(UINT8(p) + EXTRA)
|
||||
#define BASE(p) PVOID(UINT8(p) - EXTRA)
|
||||
#define SIZE(p) ((size_t *) (p))
|
||||
#define CCHAR(p) ((const char **) (p))
|
||||
#define TYPE(p) CCHAR(UINT8(p) + sizeof (size_t))
|
||||
|
||||
void initAlloc(void)
|
||||
{
|
||||
allocations = g_ptr_array_new();
|
||||
}
|
||||
|
||||
static void uninitComplain(gpointer ptr, gpointer data)
|
||||
{
|
||||
char *str2 = NULL;
|
||||
char **str = (char **)data;
|
||||
|
||||
if (*str == NULL)
|
||||
*str = g_strdup_printf("");
|
||||
str2 = g_strdup_printf("%s%p %s\n", *str, ptr, *TYPE(ptr));
|
||||
g_free(*str);
|
||||
*str = str2;
|
||||
}
|
||||
|
||||
void uninitAlloc(void)
|
||||
{
|
||||
char *str = NULL;
|
||||
|
||||
if (allocations->len == 0)
|
||||
{
|
||||
g_ptr_array_free(allocations, TRUE);
|
||||
return;
|
||||
}
|
||||
|
||||
g_ptr_array_foreach(allocations, uninitComplain, &str);
|
||||
userbug("Some data was leaked; either you left a uiControl lying around or there's a bug in libui itself. Leaked data:\n%s", str);
|
||||
g_free(str);
|
||||
}
|
||||
|
||||
void *uiAlloc(size_t size, const char *type)
|
||||
{
|
||||
void *out = g_malloc0(EXTRA + size);
|
||||
|
||||
*SIZE(out) = size;
|
||||
*TYPE(out) = type;
|
||||
g_ptr_array_add(allocations, out);
|
||||
return DATA(out);
|
||||
}
|
||||
|
||||
void *uiRealloc(void *p, size_t new, const char *type)
|
||||
{
|
||||
void *out;
|
||||
size_t *s;
|
||||
|
||||
if (p == NULL)
|
||||
return uiAlloc(new, type);
|
||||
p = BASE(p);
|
||||
out = g_realloc(p, EXTRA + new);
|
||||
s = SIZE(out);
|
||||
if (new <= *s)
|
||||
memset(((uint8_t *) DATA(out)) + *s, 0, new - *s);
|
||||
*s = new;
|
||||
if (g_ptr_array_remove(allocations, p) == FALSE)
|
||||
implbug("%p not found in allocations array in uiRealloc()", p);
|
||||
g_ptr_array_add(allocations, out);
|
||||
return DATA(out);
|
||||
}
|
||||
|
||||
void uiFree(void *p)
|
||||
{
|
||||
if (p == NULL)
|
||||
implbug("attempt to uiFree(NULL)");
|
||||
p = BASE(p);
|
||||
g_free(p);
|
||||
if (g_ptr_array_remove(allocations, p) == FALSE)
|
||||
implbug("%p not found in allocations array in uiFree()", p);
|
||||
}
|
||||
Vendored
+664
@@ -0,0 +1,664 @@
|
||||
// 4 september 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
// notes:
|
||||
// - G_DECLARE_DERIVABLE/FINAL_INTERFACE() requires glib 2.44 and that's starting with debian stretch (testing) (GTK+ 3.18) and ubuntu 15.04 (GTK+ 3.14) - debian jessie has 2.42 (GTK+ 3.14)
|
||||
#define areaWidgetType (areaWidget_get_type())
|
||||
#define areaWidget(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), areaWidgetType, areaWidget))
|
||||
#define isAreaWidget(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), areaWidgetType))
|
||||
#define areaWidgetClass(class) (G_TYPE_CHECK_CLASS_CAST((class), areaWidgetType, areaWidgetClass))
|
||||
#define isAreaWidgetClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), areaWidget))
|
||||
#define getAreaWidgetClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), areaWidgetType, areaWidgetClass))
|
||||
|
||||
typedef struct areaWidget areaWidget;
|
||||
typedef struct areaWidgetClass areaWidgetClass;
|
||||
|
||||
struct areaWidget {
|
||||
GtkDrawingArea parent_instance;
|
||||
uiArea *a;
|
||||
// construct-only parameters aare not set until after the init() function has returned
|
||||
// we need this particular object available during init(), so put it here instead of in uiArea
|
||||
// keep a pointer in uiArea for convenience, though
|
||||
clickCounter cc;
|
||||
};
|
||||
|
||||
struct areaWidgetClass {
|
||||
GtkDrawingAreaClass parent_class;
|
||||
};
|
||||
|
||||
struct uiArea {
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget; // either swidget or areaWidget depending on whether it is scrolling
|
||||
|
||||
GtkWidget *swidget;
|
||||
GtkContainer *scontainer;
|
||||
GtkScrolledWindow *sw;
|
||||
|
||||
GtkWidget *areaWidget;
|
||||
GtkDrawingArea *drawingArea;
|
||||
areaWidget *area;
|
||||
|
||||
uiAreaHandler *ah;
|
||||
|
||||
gboolean scrolling;
|
||||
int scrollWidth;
|
||||
int scrollHeight;
|
||||
|
||||
// note that this is a pointer; see above
|
||||
clickCounter *cc;
|
||||
|
||||
// for user window drags
|
||||
GdkEventButton *dragevent;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(areaWidget, areaWidget, GTK_TYPE_DRAWING_AREA)
|
||||
|
||||
static void areaWidget_init(areaWidget *aw)
|
||||
{
|
||||
// for events
|
||||
gtk_widget_add_events(GTK_WIDGET(aw),
|
||||
GDK_POINTER_MOTION_MASK |
|
||||
GDK_BUTTON_MOTION_MASK |
|
||||
GDK_BUTTON_PRESS_MASK |
|
||||
GDK_BUTTON_RELEASE_MASK |
|
||||
GDK_KEY_PRESS_MASK |
|
||||
GDK_KEY_RELEASE_MASK |
|
||||
GDK_ENTER_NOTIFY_MASK |
|
||||
GDK_LEAVE_NOTIFY_MASK);
|
||||
|
||||
gtk_widget_set_can_focus(GTK_WIDGET(aw), TRUE);
|
||||
|
||||
clickCounterReset(&(aw->cc));
|
||||
}
|
||||
|
||||
static void areaWidget_dispose(GObject *obj)
|
||||
{
|
||||
G_OBJECT_CLASS(areaWidget_parent_class)->dispose(obj);
|
||||
}
|
||||
|
||||
static void areaWidget_finalize(GObject *obj)
|
||||
{
|
||||
G_OBJECT_CLASS(areaWidget_parent_class)->finalize(obj);
|
||||
}
|
||||
|
||||
static void areaWidget_size_allocate(GtkWidget *w, GtkAllocation *allocation)
|
||||
{
|
||||
areaWidget *aw = areaWidget(w);
|
||||
uiArea *a = aw->a;
|
||||
|
||||
// GtkDrawingArea has a size_allocate() implementation; we need to call it
|
||||
// this will call gtk_widget_set_allocation() for us
|
||||
GTK_WIDGET_CLASS(areaWidget_parent_class)->size_allocate(w, allocation);
|
||||
|
||||
if (!a->scrolling)
|
||||
// we must redraw everything on resize because Windows requires it
|
||||
gtk_widget_queue_resize(w);
|
||||
}
|
||||
|
||||
static void loadAreaSize(uiArea *a, double *width, double *height)
|
||||
{
|
||||
GtkAllocation allocation;
|
||||
|
||||
*width = 0;
|
||||
*height = 0;
|
||||
// don't provide size information for scrolling areas
|
||||
if (!a->scrolling) {
|
||||
gtk_widget_get_allocation(a->areaWidget, &allocation);
|
||||
// these are already in drawing space coordinates
|
||||
// for drawing, the size of drawing space has the same value as the widget allocation
|
||||
// thanks to tristan in irc.gimp.net/#gtk+
|
||||
*width = allocation.width;
|
||||
*height = allocation.height;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean areaWidget_draw(GtkWidget *w, cairo_t *cr)
|
||||
{
|
||||
uiAreaDrawParams dp;
|
||||
double clipX0, clipY0, clipX1, clipY1;
|
||||
areaWidget *aw = areaWidget(w);
|
||||
uiArea *a = aw->a;
|
||||
|
||||
dp.Context = newContext(cr);
|
||||
|
||||
loadAreaSize(a, &(dp.AreaWidth), &(dp.AreaHeight));
|
||||
|
||||
cairo_clip_extents(cr, &clipX0, &clipY0, &clipX1, &clipY1);
|
||||
dp.ClipX = clipX0;
|
||||
dp.ClipY = clipY0;
|
||||
dp.ClipWidth = clipX1 - clipX0;
|
||||
dp.ClipHeight = clipY1 - clipY0;
|
||||
|
||||
/* no need to save or restore the graphics state
|
||||
* to reset transformations; GTK+ does that for us */
|
||||
(*(a->ah->Draw))(a->ah, a, &dp);
|
||||
|
||||
freeContext(dp.Context);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* to do this properly for scrolling areas, we need to
|
||||
* - return the same value for min and nat
|
||||
* - call gtk_widget_queue_resize() when the size changes
|
||||
* thanks to Company in irc.gimp.net/#gtk+ */
|
||||
static void areaWidget_get_preferred_height(GtkWidget *w,
|
||||
gint *min, gint *nat)
|
||||
{
|
||||
areaWidget *aw = areaWidget(w);
|
||||
uiArea *a = aw->a;
|
||||
|
||||
// always chain up just in case
|
||||
GTK_WIDGET_CLASS(areaWidget_parent_class)->
|
||||
get_preferred_height(w, min, nat);
|
||||
|
||||
if (a->scrolling)
|
||||
{
|
||||
*min = a->scrollHeight;
|
||||
*nat = a->scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
static void areaWidget_get_preferred_width(GtkWidget *w, gint *min, gint *nat)
|
||||
{
|
||||
areaWidget *aw = areaWidget(w);
|
||||
uiArea *a = aw->a;
|
||||
|
||||
/* always chain up just in case */
|
||||
GTK_WIDGET_CLASS(areaWidget_parent_class)->get_preferred_width(w, min, nat);
|
||||
if (a->scrolling)
|
||||
{
|
||||
*min = a->scrollWidth;
|
||||
*nat = a->scrollWidth;
|
||||
}
|
||||
}
|
||||
|
||||
static guint translateModifiers(guint state, GdkWindow *window)
|
||||
{
|
||||
GdkModifierType statetype;
|
||||
|
||||
/* GDK doesn't initialize the modifier flags fully; we have to
|
||||
* explicitly tell it to (thanks to Daniel_S and daniels
|
||||
* (two different people) in irc.gimp.net/#gtk+) */
|
||||
statetype = state;
|
||||
gdk_keymap_add_virtual_modifiers(
|
||||
gdk_keymap_get_for_display(gdk_window_get_display(window)),
|
||||
&statetype);
|
||||
return statetype;
|
||||
}
|
||||
|
||||
static uiModifiers toModifiers(guint state)
|
||||
{
|
||||
uiModifiers m = 0;
|
||||
|
||||
if ((state & GDK_CONTROL_MASK) != 0)
|
||||
m |= uiModifierCtrl;
|
||||
if ((state & GDK_META_MASK) != 0)
|
||||
m |= uiModifierAlt;
|
||||
/* GTK+ itself requires this to be Alt (just read through gtkaccelgroup.c) */
|
||||
if ((state & GDK_MOD1_MASK) != 0)
|
||||
m |= uiModifierAlt;
|
||||
if ((state & GDK_SHIFT_MASK) != 0)
|
||||
m |= uiModifierShift;
|
||||
if ((state & GDK_SUPER_MASK) != 0)
|
||||
m |= uiModifierSuper;
|
||||
return m;
|
||||
}
|
||||
|
||||
/* capture on drag is done automatically on GTK+ */
|
||||
static void finishMouseEvent(uiArea *a, uiAreaMouseEvent *me, guint mb, gdouble x, gdouble y, guint state, GdkWindow *window)
|
||||
{
|
||||
/* on GTK+, mouse buttons 4-7 are for scrolling; if we got here, that's a mistake */
|
||||
if (mb >= 4 && mb <= 7)
|
||||
return;
|
||||
/* if the button ID >= 8, continue counting from 4, as in the MouseEvent spec */
|
||||
if (me->Down >= 8)
|
||||
me->Down -= 4;
|
||||
if (me->Up >= 8)
|
||||
me->Up -= 4;
|
||||
|
||||
state = translateModifiers(state, window);
|
||||
me->Modifiers = toModifiers(state);
|
||||
|
||||
/* the mb != # checks exclude the Up/Down button from Held */
|
||||
me->Held1To64 = 0;
|
||||
if (mb != 1 && (state & GDK_BUTTON1_MASK) != 0)
|
||||
me->Held1To64 |= 1 << 0;
|
||||
if (mb != 2 && (state & GDK_BUTTON2_MASK) != 0)
|
||||
me->Held1To64 |= 1 << 1;
|
||||
if (mb != 3 && (state & GDK_BUTTON3_MASK) != 0)
|
||||
me->Held1To64 |= 1 << 2;
|
||||
|
||||
/* don't check GDK_BUTTON4_MASK or GDK_BUTTON5_MASK because those
|
||||
* are for the scrolling buttons mentioned above
|
||||
*
|
||||
* GDK expressly does not support any more buttons in the
|
||||
* GdkModifierType; see
|
||||
* https://git.gnome.org/browse/gtk+/tree/gdk/x11/gdkdevice-xi2.c#n763
|
||||
* (thanks mclasen in irc.gimp.net/#gtk+)
|
||||
*/
|
||||
|
||||
/* these are already in drawing space coordinates
|
||||
* the size of drawing space has the same value as the widget allocation
|
||||
* thanks to tristan in irc.gimp.net/#gtk+ */
|
||||
me->X = x;
|
||||
me->Y = y;
|
||||
|
||||
loadAreaSize(a, &(me->AreaWidth), &(me->AreaHeight));
|
||||
|
||||
(*(a->ah->MouseEvent))(a->ah, a, me);
|
||||
}
|
||||
|
||||
static gboolean areaWidget_button_press_event(GtkWidget *w, GdkEventButton *e)
|
||||
{
|
||||
gint maxTime, maxDistance;
|
||||
uiAreaMouseEvent me;
|
||||
areaWidget *aw = areaWidget(w);
|
||||
uiArea *a = aw->a;
|
||||
GtkSettings *settings = NULL;
|
||||
|
||||
/* clicking doesn't automatically transfer keyboard focus;
|
||||
* we must do so manually (thanks tristan in irc.gimp.net/#gtk+) */
|
||||
gtk_widget_grab_focus(w);
|
||||
|
||||
/* we handle multiple clicks ourselves here,
|
||||
* in the same way as we do on Windows */
|
||||
|
||||
/* ignore GDK's generated double-clicks and beyond */
|
||||
if (e->type != GDK_BUTTON_PRESS)
|
||||
return GDK_EVENT_PROPAGATE;
|
||||
|
||||
settings = gtk_widget_get_settings(w);
|
||||
|
||||
g_object_get(settings,
|
||||
"gtk-double-click-time", &maxTime,
|
||||
"gtk-double-click-distance", &maxDistance,
|
||||
NULL);
|
||||
|
||||
/* don't unref settings; it's transfer-none (thanks gregier in irc.gimp.net/#gtk+)
|
||||
* e->time is guint32
|
||||
* e->x and e->y are floating-point; just make them 32-bit integers
|
||||
* maxTime and maxDistance... are gint, which *should* fit, hopefully... */
|
||||
me.Count = clickCounterClick(a->cc, me.Down,
|
||||
e->x, e->y,
|
||||
e->time, maxTime,
|
||||
maxDistance, maxDistance);
|
||||
|
||||
me.Down = e->button;
|
||||
me.Up = 0;
|
||||
|
||||
/* and set things up for window drags */
|
||||
a->dragevent = e;
|
||||
finishMouseEvent(a, &me, e->button, e->x, e->y, e->state, e->window);
|
||||
a->dragevent = NULL;
|
||||
return GDK_EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
static gboolean areaWidget_button_release_event(GtkWidget *w, GdkEventButton *e)
|
||||
{
|
||||
uiAreaMouseEvent me;
|
||||
areaWidget *aw = areaWidget(w);
|
||||
uiArea *a = aw->a;
|
||||
|
||||
me.Down = 0;
|
||||
me.Up = e->button;
|
||||
me.Count = 0;
|
||||
finishMouseEvent(a, &me, e->button, e->x, e->y, e->state, e->window);
|
||||
return GDK_EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
static gboolean areaWidget_motion_notify_event(GtkWidget *w, GdkEventMotion *e)
|
||||
{
|
||||
uiAreaMouseEvent me;
|
||||
areaWidget *aw = areaWidget(w);
|
||||
uiArea *a = aw->a;
|
||||
|
||||
me.Down = 0;
|
||||
me.Up = 0;
|
||||
me.Count = 0;
|
||||
finishMouseEvent(a, &me, 0, e->x, e->y, e->state, e->window);
|
||||
return GDK_EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
/* we want switching away from the control to reset the double-click counter,
|
||||
* like with WM_ACTIVATE on Windows
|
||||
*
|
||||
* according to tristan in irc.gimp.net/#gtk+, doing this on both
|
||||
* enter-notify-event and leave-notify-event is correct (and it seems to be
|
||||
* true in my own tests; plus the events DO get sent when switching programs
|
||||
* with the keyboard (just pointing that out)) */
|
||||
static gboolean onCrossing(areaWidget *aw, int left)
|
||||
{
|
||||
uiArea *a = aw->a;
|
||||
|
||||
(*(a->ah->MouseCrossed))(a->ah, a, left);
|
||||
clickCounterReset(a->cc);
|
||||
return GDK_EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
static gboolean areaWidget_enter_notify_event(GtkWidget *w, GdkEventCrossing *e)
|
||||
{
|
||||
return onCrossing(areaWidget(w), 0);
|
||||
}
|
||||
|
||||
static gboolean areaWidget_leave_notify_event(GtkWidget *w, GdkEventCrossing *e)
|
||||
{
|
||||
return onCrossing(areaWidget(w), 1);
|
||||
}
|
||||
|
||||
/* note: there is no equivalent to WM_CAPTURECHANGED on GTK+; there literally
|
||||
* is no way to break a grab like that (at least not on X11 and Wayland)
|
||||
*
|
||||
* even if I invoke the task switcher and switch processes, the mouse grab will
|
||||
* still be held until I let go of all buttons
|
||||
* therefore, no DragBroken()
|
||||
*
|
||||
* we use GDK_KEY_Print as a sentinel because libui will never support the print screen key; that key belongs to the user
|
||||
*/
|
||||
|
||||
static const struct {
|
||||
guint keyval;
|
||||
uiExtKey extkey;
|
||||
} extKeys[] = {
|
||||
{ GDK_KEY_Escape, uiExtKeyEscape },
|
||||
{ GDK_KEY_Insert, uiExtKeyInsert },
|
||||
{ GDK_KEY_Delete, uiExtKeyDelete },
|
||||
{ GDK_KEY_Home, uiExtKeyHome },
|
||||
{ GDK_KEY_End, uiExtKeyEnd },
|
||||
{ GDK_KEY_Page_Up, uiExtKeyPageUp },
|
||||
{ GDK_KEY_Page_Down, uiExtKeyPageDown },
|
||||
{ GDK_KEY_Up, uiExtKeyUp },
|
||||
{ GDK_KEY_Down, uiExtKeyDown },
|
||||
{ GDK_KEY_Left, uiExtKeyLeft },
|
||||
{ GDK_KEY_Right, uiExtKeyRight },
|
||||
{ GDK_KEY_F1, uiExtKeyF1 },
|
||||
{ GDK_KEY_F2, uiExtKeyF2 },
|
||||
{ GDK_KEY_F3, uiExtKeyF3 },
|
||||
{ GDK_KEY_F4, uiExtKeyF4 },
|
||||
{ GDK_KEY_F5, uiExtKeyF5 },
|
||||
{ GDK_KEY_F6, uiExtKeyF6 },
|
||||
{ GDK_KEY_F7, uiExtKeyF7 },
|
||||
{ GDK_KEY_F8, uiExtKeyF8 },
|
||||
{ GDK_KEY_F9, uiExtKeyF9 },
|
||||
{ GDK_KEY_F10, uiExtKeyF10 },
|
||||
{ GDK_KEY_F11, uiExtKeyF11 },
|
||||
{ GDK_KEY_F12, uiExtKeyF12 },
|
||||
/* numpad numeric keys and . are handled in events.c */
|
||||
{ GDK_KEY_KP_Enter, uiExtKeyNEnter },
|
||||
{ GDK_KEY_KP_Add, uiExtKeyNAdd },
|
||||
{ GDK_KEY_KP_Subtract, uiExtKeyNSubtract },
|
||||
{ GDK_KEY_KP_Multiply, uiExtKeyNMultiply },
|
||||
{ GDK_KEY_KP_Divide, uiExtKeyNDivide },
|
||||
{ GDK_KEY_Print, 0 },
|
||||
};
|
||||
|
||||
static const struct {
|
||||
guint keyval;
|
||||
uiModifiers mod;
|
||||
} modKeys[] = {
|
||||
{ GDK_KEY_Control_L, uiModifierCtrl },
|
||||
{ GDK_KEY_Control_R, uiModifierCtrl },
|
||||
{ GDK_KEY_Alt_L, uiModifierAlt },
|
||||
{ GDK_KEY_Alt_R, uiModifierAlt },
|
||||
{ GDK_KEY_Meta_L, uiModifierAlt },
|
||||
{ GDK_KEY_Meta_R, uiModifierAlt },
|
||||
{ GDK_KEY_Shift_L, uiModifierShift },
|
||||
{ GDK_KEY_Shift_R, uiModifierShift },
|
||||
{ GDK_KEY_Super_L, uiModifierSuper },
|
||||
{ GDK_KEY_Super_R, uiModifierSuper },
|
||||
{ GDK_KEY_Print, 0 },
|
||||
};
|
||||
|
||||
static int areaKeyEvent(uiArea *a, int up, GdkEventKey *e)
|
||||
{
|
||||
uiAreaKeyEvent ke;
|
||||
guint state;
|
||||
int i;
|
||||
|
||||
ke.Key = 0;
|
||||
ke.ExtKey = 0;
|
||||
ke.Modifier = 0;
|
||||
|
||||
state = translateModifiers(e->state, e->window);
|
||||
ke.Modifiers = toModifiers(state);
|
||||
|
||||
ke.Up = up;
|
||||
|
||||
for (i = 0; extKeys[i].keyval != GDK_KEY_Print; i++)
|
||||
if (extKeys[i].keyval == e->keyval)
|
||||
{
|
||||
ke.ExtKey = extKeys[i].extkey;
|
||||
goto keyFound;
|
||||
}
|
||||
|
||||
for (i = 0; modKeys[i].keyval != GDK_KEY_Print; i++)
|
||||
if (modKeys[i].keyval == e->keyval)
|
||||
{
|
||||
ke.Modifier = modKeys[i].mod;
|
||||
/* don't include the modifier in ke.Modifiers */
|
||||
ke.Modifiers &= ~ke.Modifier;
|
||||
goto keyFound;
|
||||
}
|
||||
|
||||
if (fromScancode(e->hardware_keycode - 8, &ke))
|
||||
goto keyFound;
|
||||
|
||||
/* no supported key found; treat as unhandled */
|
||||
return 0;
|
||||
|
||||
keyFound:
|
||||
return (*(a->ah->KeyEvent))(a->ah, a, &ke);
|
||||
}
|
||||
|
||||
static gboolean areaWidget_key_press_event(GtkWidget *w, GdkEventKey *e)
|
||||
{
|
||||
areaWidget *aw = areaWidget(w);
|
||||
uiArea *a = aw->a;
|
||||
|
||||
if (areaKeyEvent(a, 0, e))
|
||||
return GDK_EVENT_STOP;
|
||||
return GDK_EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
static gboolean areaWidget_key_release_event(GtkWidget *w, GdkEventKey *e)
|
||||
{
|
||||
areaWidget *aw = areaWidget(w);
|
||||
uiArea *a = aw->a;
|
||||
|
||||
if (areaKeyEvent(a, 1, e))
|
||||
return GDK_EVENT_STOP;
|
||||
return GDK_EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
enum {
|
||||
pArea = 1,
|
||||
nProps,
|
||||
};
|
||||
|
||||
static GParamSpec *pspecArea;
|
||||
|
||||
static void areaWidget_set_property(GObject *obj, guint prop, const GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
areaWidget *aw = areaWidget(obj);
|
||||
|
||||
switch (prop)
|
||||
{
|
||||
case pArea:
|
||||
aw->a = (uiArea *) g_value_get_pointer(value);
|
||||
aw->a->cc = &(aw->cc);
|
||||
return;
|
||||
}
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop, pspec);
|
||||
}
|
||||
|
||||
static void areaWidget_get_property(GObject *obj, guint prop, GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop, pspec);
|
||||
}
|
||||
|
||||
static void areaWidget_class_init(areaWidgetClass *class)
|
||||
{
|
||||
G_OBJECT_CLASS(class)->dispose = areaWidget_dispose;
|
||||
G_OBJECT_CLASS(class)->finalize = areaWidget_finalize;
|
||||
G_OBJECT_CLASS(class)->set_property = areaWidget_set_property;
|
||||
G_OBJECT_CLASS(class)->get_property = areaWidget_get_property;
|
||||
|
||||
GTK_WIDGET_CLASS(class)->size_allocate = areaWidget_size_allocate;
|
||||
GTK_WIDGET_CLASS(class)->draw = areaWidget_draw;
|
||||
GTK_WIDGET_CLASS(class)->get_preferred_height = areaWidget_get_preferred_height;
|
||||
GTK_WIDGET_CLASS(class)->get_preferred_width = areaWidget_get_preferred_width;
|
||||
GTK_WIDGET_CLASS(class)->button_press_event = areaWidget_button_press_event;
|
||||
GTK_WIDGET_CLASS(class)->button_release_event = areaWidget_button_release_event;
|
||||
GTK_WIDGET_CLASS(class)->motion_notify_event = areaWidget_motion_notify_event;
|
||||
GTK_WIDGET_CLASS(class)->enter_notify_event = areaWidget_enter_notify_event;
|
||||
GTK_WIDGET_CLASS(class)->leave_notify_event = areaWidget_leave_notify_event;
|
||||
GTK_WIDGET_CLASS(class)->key_press_event = areaWidget_key_press_event;
|
||||
GTK_WIDGET_CLASS(class)->key_release_event = areaWidget_key_release_event;
|
||||
|
||||
pspecArea = g_param_spec_pointer("libui-area",
|
||||
"libui-area",
|
||||
"uiArea.",
|
||||
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
|
||||
g_object_class_install_property(G_OBJECT_CLASS(class), pArea, pspecArea);
|
||||
}
|
||||
|
||||
// control implementation
|
||||
|
||||
uiUnixControlAllDefaults(uiArea)
|
||||
|
||||
void uiAreaSetSize(uiArea *a, int width, int height)
|
||||
{
|
||||
if (!a->scrolling)
|
||||
userbug("You cannot call uiAreaSetSize() on a non-scrolling uiArea. (area: %p)", a);
|
||||
a->scrollWidth = width;
|
||||
a->scrollHeight = height;
|
||||
gtk_widget_queue_resize(a->areaWidget);
|
||||
}
|
||||
|
||||
void uiAreaQueueRedrawAll(uiArea *a)
|
||||
{
|
||||
gtk_widget_queue_draw(a->areaWidget);
|
||||
}
|
||||
|
||||
void uiAreaScrollTo(uiArea *a, double x, double y, double width, double height)
|
||||
{
|
||||
/* TODO
|
||||
* TODO adjust adjustments and find source for that */
|
||||
}
|
||||
|
||||
void uiAreaBeginUserWindowMove(uiArea *a)
|
||||
{
|
||||
GtkWidget *toplevel;
|
||||
|
||||
if (a->dragevent == NULL)
|
||||
userbug("cannot call uiAreaBeginUserWindowMove() outside of a Mouse() with Down != 0");
|
||||
/* TODO don't we have a libui function for this? did I scrap it?
|
||||
* TODO widget or areaWidget? */
|
||||
toplevel = gtk_widget_get_toplevel(a->widget);
|
||||
// TODO
|
||||
if (toplevel == NULL)
|
||||
return;
|
||||
/* the docs say to do this */
|
||||
|
||||
/* TODO */
|
||||
if (!gtk_widget_is_toplevel(toplevel))
|
||||
return;
|
||||
/* TODO */
|
||||
if (!GTK_IS_WINDOW(toplevel))
|
||||
return;
|
||||
gtk_window_begin_move_drag(GTK_WINDOW(toplevel),
|
||||
a->dragevent->button,
|
||||
a->dragevent->x_root, /* TODO are these correct? */
|
||||
a->dragevent->y_root,
|
||||
a->dragevent->time);
|
||||
}
|
||||
|
||||
static const GdkWindowEdge edges[] = {
|
||||
[uiWindowResizeEdgeLeft] = GDK_WINDOW_EDGE_WEST,
|
||||
[uiWindowResizeEdgeTop] = GDK_WINDOW_EDGE_NORTH,
|
||||
[uiWindowResizeEdgeRight] = GDK_WINDOW_EDGE_EAST,
|
||||
[uiWindowResizeEdgeBottom] = GDK_WINDOW_EDGE_SOUTH,
|
||||
[uiWindowResizeEdgeTopLeft] = GDK_WINDOW_EDGE_NORTH_WEST,
|
||||
[uiWindowResizeEdgeTopRight] = GDK_WINDOW_EDGE_NORTH_EAST,
|
||||
[uiWindowResizeEdgeBottomLeft] = GDK_WINDOW_EDGE_SOUTH_WEST,
|
||||
[uiWindowResizeEdgeBottomRight] = GDK_WINDOW_EDGE_SOUTH_EAST,
|
||||
};
|
||||
|
||||
void uiAreaBeginUserWindowResize(uiArea *a, uiWindowResizeEdge edge)
|
||||
{
|
||||
GtkWidget *toplevel;
|
||||
|
||||
if (a->dragevent == NULL)
|
||||
userbug("cannot call uiAreaBeginUserWindowResize() outside of a Mouse() with Down != 0");
|
||||
/* TODO don't we have a libui function for this? did I scrap it?
|
||||
* TODO widget or areaWidget? */
|
||||
toplevel = gtk_widget_get_toplevel(a->widget);
|
||||
|
||||
/* TODO */
|
||||
if (toplevel == NULL)
|
||||
return;
|
||||
/* the docs say to do this */
|
||||
|
||||
/* TODO */
|
||||
if (!gtk_widget_is_toplevel(toplevel))
|
||||
return;
|
||||
/* TODO */
|
||||
if (!GTK_IS_WINDOW(toplevel))
|
||||
return;
|
||||
gtk_window_begin_resize_drag(GTK_WINDOW(toplevel),
|
||||
edges[edge],
|
||||
a->dragevent->button,
|
||||
a->dragevent->x_root, /* TODO are these correct? */
|
||||
a->dragevent->y_root,
|
||||
a->dragevent->time);
|
||||
}
|
||||
|
||||
uiArea *uiNewArea(uiAreaHandler *ah)
|
||||
{
|
||||
uiArea *a;
|
||||
|
||||
uiUnixNewControl(uiArea, a);
|
||||
|
||||
a->ah = ah;
|
||||
a->scrolling = FALSE;
|
||||
|
||||
a->areaWidget = GTK_WIDGET(g_object_new(areaWidgetType,
|
||||
"libui-area", a,
|
||||
NULL));
|
||||
a->drawingArea = GTK_DRAWING_AREA(a->areaWidget);
|
||||
a->area = areaWidget(a->areaWidget);
|
||||
|
||||
a->widget = a->areaWidget;
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
uiArea *uiNewScrollingArea(uiAreaHandler *ah, int width, int height)
|
||||
{
|
||||
uiArea *a;
|
||||
|
||||
uiUnixNewControl(uiArea, a);
|
||||
|
||||
a->ah = ah;
|
||||
a->scrolling = TRUE;
|
||||
a->scrollWidth = width;
|
||||
a->scrollHeight = height;
|
||||
|
||||
a->swidget = gtk_scrolled_window_new(NULL, NULL);
|
||||
a->scontainer = GTK_CONTAINER(a->swidget);
|
||||
a->sw = GTK_SCROLLED_WINDOW(a->swidget);
|
||||
|
||||
a->areaWidget = GTK_WIDGET(g_object_new(areaWidgetType,
|
||||
"libui-area", a,
|
||||
NULL));
|
||||
a->drawingArea = GTK_DRAWING_AREA(a->areaWidget);
|
||||
a->area = areaWidget(a->areaWidget);
|
||||
|
||||
a->widget = a->swidget;
|
||||
|
||||
gtk_container_add(a->scontainer, a->areaWidget);
|
||||
/* and make the area visible; only the scrolled
|
||||
* window's visibility is controlled by libui */
|
||||
gtk_widget_show(a->areaWidget);
|
||||
|
||||
return a;
|
||||
}
|
||||
Vendored
+159
@@ -0,0 +1,159 @@
|
||||
// 7 april 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct boxChild
|
||||
{
|
||||
uiControl *c;
|
||||
int stretchy;
|
||||
gboolean oldhexpand;
|
||||
GtkAlign oldhalign;
|
||||
gboolean oldvexpand;
|
||||
GtkAlign oldvalign;
|
||||
};
|
||||
|
||||
struct uiBox
|
||||
{
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget;
|
||||
GtkContainer *container;
|
||||
GtkBox *box;
|
||||
GArray *controls;
|
||||
int vertical;
|
||||
int padded;
|
||||
GtkSizeGroup *stretchygroup; /* ensures all stretchy controls have the same size */
|
||||
};
|
||||
|
||||
uiUnixControlAllDefaultsExceptDestroy(uiBox)
|
||||
|
||||
#define ctrl(b, i) &g_array_index(b->controls, struct boxChild, i)
|
||||
|
||||
static void uiBoxDestroy(uiControl *c)
|
||||
{
|
||||
uiBox *b = uiBox(c);
|
||||
struct boxChild *bc;
|
||||
guint i;
|
||||
|
||||
/* kill the size group */
|
||||
g_object_unref(b->stretchygroup);
|
||||
/* free all controls */
|
||||
for (i = 0; i < b->controls->len; i++)
|
||||
{
|
||||
bc = ctrl(b, i);
|
||||
uiControlSetParent(bc->c, NULL);
|
||||
/* and make sure the widget itself stays alive */
|
||||
uiUnixControlSetContainer(uiUnixControl(bc->c), b->container, TRUE);
|
||||
uiControlDestroy(bc->c);
|
||||
}
|
||||
g_array_free(b->controls, TRUE);
|
||||
/* and then ourselves */
|
||||
g_object_unref(b->widget);
|
||||
uiFreeControl(uiControl(b));
|
||||
}
|
||||
|
||||
void uiBoxAppend(uiBox *b, uiControl *c, int stretchy)
|
||||
{
|
||||
struct boxChild bc;
|
||||
GtkWidget *widget = NULL;
|
||||
|
||||
bc.c = c;
|
||||
bc.stretchy = stretchy;
|
||||
widget = GTK_WIDGET(uiControlHandle(bc.c));
|
||||
bc.oldhexpand = gtk_widget_get_hexpand(widget);
|
||||
bc.oldhalign = gtk_widget_get_halign(widget);
|
||||
bc.oldvexpand = gtk_widget_get_vexpand(widget);
|
||||
bc.oldvalign = gtk_widget_get_valign(widget);
|
||||
|
||||
if (bc.stretchy) {
|
||||
if (b->vertical) {
|
||||
gtk_widget_set_vexpand(widget, TRUE);
|
||||
gtk_widget_set_valign(widget, GTK_ALIGN_FILL);
|
||||
} else {
|
||||
gtk_widget_set_hexpand(widget, TRUE);
|
||||
gtk_widget_set_halign(widget, GTK_ALIGN_FILL);
|
||||
}
|
||||
gtk_size_group_add_widget(b->stretchygroup, widget);
|
||||
} else
|
||||
if (b->vertical)
|
||||
gtk_widget_set_vexpand(widget, FALSE);
|
||||
else
|
||||
gtk_widget_set_hexpand(widget, FALSE);
|
||||
/* and make them fill the opposite direction */
|
||||
if (b->vertical) {
|
||||
gtk_widget_set_hexpand(widget, TRUE);
|
||||
gtk_widget_set_halign(widget, GTK_ALIGN_FILL);
|
||||
} else {
|
||||
gtk_widget_set_vexpand(widget, TRUE);
|
||||
gtk_widget_set_valign(widget, GTK_ALIGN_FILL);
|
||||
}
|
||||
|
||||
uiControlSetParent(bc.c, uiControl(b));
|
||||
uiUnixControlSetContainer(uiUnixControl(bc.c), b->container, FALSE);
|
||||
g_array_append_val(b->controls, bc);
|
||||
}
|
||||
|
||||
void uiBoxDelete(uiBox *b, int index)
|
||||
{
|
||||
struct boxChild *bc = ctrl(b, index);
|
||||
GtkWidget *widget = GTK_WIDGET(uiControlHandle(bc->c));
|
||||
|
||||
uiControlSetParent(bc->c, NULL);
|
||||
uiUnixControlSetContainer(uiUnixControl(bc->c), b->container, TRUE);
|
||||
|
||||
if (bc->stretchy)
|
||||
gtk_size_group_remove_widget(b->stretchygroup, widget);
|
||||
gtk_widget_set_hexpand(widget, bc->oldhexpand);
|
||||
gtk_widget_set_halign(widget, bc->oldhalign);
|
||||
gtk_widget_set_vexpand(widget, bc->oldvexpand);
|
||||
gtk_widget_set_valign(widget, bc->oldvalign);
|
||||
|
||||
g_array_remove_index(b->controls, index);
|
||||
}
|
||||
|
||||
int uiBoxPadded(uiBox *b)
|
||||
{
|
||||
return b->padded;
|
||||
}
|
||||
|
||||
void uiBoxSetPadded(uiBox *b, int padded)
|
||||
{
|
||||
b->padded = padded;
|
||||
if (b->padded)
|
||||
if (b->vertical)
|
||||
gtk_box_set_spacing(b->box, gtkYPadding);
|
||||
else
|
||||
gtk_box_set_spacing(b->box, gtkXPadding);
|
||||
else
|
||||
gtk_box_set_spacing(b->box, 0);
|
||||
}
|
||||
|
||||
static uiBox *finishNewBox(GtkOrientation orientation)
|
||||
{
|
||||
uiBox *b;
|
||||
|
||||
uiUnixNewControl(uiBox, b);
|
||||
|
||||
b->widget = gtk_box_new(orientation, 0);
|
||||
b->container = GTK_CONTAINER(b->widget);
|
||||
b->box = GTK_BOX(b->widget);
|
||||
|
||||
b->vertical = orientation == GTK_ORIENTATION_VERTICAL;
|
||||
|
||||
if (b->vertical)
|
||||
b->stretchygroup = gtk_size_group_new(GTK_SIZE_GROUP_VERTICAL);
|
||||
else
|
||||
b->stretchygroup = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
|
||||
|
||||
b->controls = g_array_new(FALSE, TRUE, sizeof (struct boxChild));
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
uiBox *uiNewHorizontalBox(void)
|
||||
{
|
||||
return finishNewBox(GTK_ORIENTATION_HORIZONTAL);
|
||||
}
|
||||
|
||||
uiBox *uiNewVerticalBox(void)
|
||||
{
|
||||
return finishNewBox(GTK_ORIENTATION_VERTICAL);
|
||||
}
|
||||
Vendored
+56
@@ -0,0 +1,56 @@
|
||||
// 10 june 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct uiButton
|
||||
{
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget;
|
||||
GtkButton *button;
|
||||
void (*onClicked)(uiButton *, void *);
|
||||
void *onClickedData;
|
||||
};
|
||||
|
||||
uiUnixControlAllDefaults(uiButton)
|
||||
|
||||
static void onClicked(GtkButton *button, gpointer data)
|
||||
{
|
||||
uiButton *b = uiButton(data);
|
||||
|
||||
(*(b->onClicked))(b, b->onClickedData);
|
||||
}
|
||||
|
||||
static void defaultOnClicked(uiButton *b, void *data)
|
||||
{
|
||||
/* do nothing */
|
||||
}
|
||||
|
||||
char *uiButtonText(uiButton *b)
|
||||
{
|
||||
return uiUnixStrdupText(gtk_button_get_label(b->button));
|
||||
}
|
||||
|
||||
void uiButtonSetText(uiButton *b, const char *text)
|
||||
{
|
||||
gtk_button_set_label(b->button, text);
|
||||
}
|
||||
|
||||
void uiButtonOnClicked(uiButton *b, void (*f)(uiButton *, void *), void *data)
|
||||
{
|
||||
b->onClicked = f;
|
||||
b->onClickedData = data;
|
||||
}
|
||||
|
||||
uiButton *uiNewButton(const char *text)
|
||||
{
|
||||
uiButton *b;
|
||||
|
||||
uiUnixNewControl(uiButton, b);
|
||||
|
||||
b->widget = gtk_button_new_with_label(text);
|
||||
b->button = GTK_BUTTON(b->widget);
|
||||
|
||||
g_signal_connect(b->widget, "clicked", G_CALLBACK(onClicked), b);
|
||||
uiButtonOnClicked(b, defaultOnClicked, NULL);
|
||||
|
||||
return b;
|
||||
}
|
||||
Vendored
+299
@@ -0,0 +1,299 @@
|
||||
// 28 june 2016
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
// TODOs
|
||||
// - it's a rather tight fit
|
||||
// - selected row text color is white
|
||||
// - resizing a column with a button in it crashes the program
|
||||
// - accessibility
|
||||
// - right side too big?
|
||||
|
||||
#define cellRendererButtonType (cellRendererButton_get_type())
|
||||
#define cellRendererButton(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), cellRendererButtonType, cellRendererButton))
|
||||
#define isCellRendererButton(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), cellRendererButtonType))
|
||||
#define cellRendererButtonClass(class) (G_TYPE_CHECK_CLASS_CAST((class), cellRendererButtonType, cellRendererButtonClass))
|
||||
#define isCellRendererButtonClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), cellRendererButton))
|
||||
#define getCellRendererButtonClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), cellRendererButtonType, cellRendererButtonClass))
|
||||
|
||||
typedef struct cellRendererButton cellRendererButton;
|
||||
typedef struct cellRendererButtonClass cellRendererButtonClass;
|
||||
|
||||
struct cellRendererButton {
|
||||
GtkCellRenderer parent_instance;
|
||||
char *text;
|
||||
};
|
||||
|
||||
struct cellRendererButtonClass {
|
||||
GtkCellRendererClass parent_class;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(cellRendererButton, cellRendererButton, GTK_TYPE_CELL_RENDERER)
|
||||
|
||||
static void cellRendererButton_init(cellRendererButton *c)
|
||||
{
|
||||
g_object_set(c, "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL);
|
||||
// the standard cell renderers all do this
|
||||
gtk_cell_renderer_set_padding(GTK_CELL_RENDERER(c), 2, 2);
|
||||
}
|
||||
|
||||
static void cellRendererButton_dispose(GObject *obj)
|
||||
{
|
||||
G_OBJECT_CLASS(cellRendererButton_parent_class)->dispose(obj);
|
||||
}
|
||||
|
||||
static void cellRendererButton_finalize(GObject *obj)
|
||||
{
|
||||
cellRendererButton *c = cellRendererButton(obj);
|
||||
|
||||
if (c->text != NULL) {
|
||||
g_free(c->text);
|
||||
c->text = NULL;
|
||||
}
|
||||
G_OBJECT_CLASS(cellRendererButton_parent_class)->finalize(obj);
|
||||
}
|
||||
|
||||
static GtkSizeRequestMode cellRendererButton_get_request_mode(GtkCellRenderer *r)
|
||||
{
|
||||
return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
|
||||
}
|
||||
|
||||
// this is basically what GtkCellRendererToggle did in 3.10 and does in 3.20, as well as what the Foreign Drawing gtk3-demo demo does
|
||||
static GtkStyleContext *setButtonStyle(GtkWidget *widget)
|
||||
{
|
||||
GtkStyleContext *base, *context;
|
||||
GtkWidgetPath *path;
|
||||
|
||||
base = gtk_widget_get_style_context(widget);
|
||||
context = gtk_style_context_new();
|
||||
|
||||
path = gtk_widget_path_copy(gtk_style_context_get_path(base));
|
||||
gtk_widget_path_append_type(path, G_TYPE_NONE);
|
||||
if (!FUTURE_gtk_widget_path_iter_set_object_name(path, -1, "button"))
|
||||
// not on 3.20; try the type
|
||||
gtk_widget_path_iter_set_object_type(path, -1, GTK_TYPE_BUTTON);
|
||||
|
||||
gtk_style_context_set_path(context, path);
|
||||
gtk_style_context_set_parent(context, base);
|
||||
// the gtk3-demo example (which says we need to do this) uses gtk_widget_path_iter_get_state(path, -1) but that's not available until 3.14
|
||||
// TODO make a future for that too
|
||||
gtk_style_context_set_state(context, gtk_style_context_get_state(base));
|
||||
gtk_widget_path_unref(path);
|
||||
|
||||
// and if the above widget path screwery stil doesn't work, this will
|
||||
gtk_style_context_add_class(context, GTK_STYLE_CLASS_BUTTON);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
void unsetButtonStyle(GtkStyleContext *context)
|
||||
{
|
||||
g_object_unref(context);
|
||||
}
|
||||
|
||||
// this is based on what GtkCellRendererText does
|
||||
static void cellRendererButton_get_preferred_width(GtkCellRenderer *r, GtkWidget *widget, gint *minimum, gint *natural)
|
||||
{
|
||||
cellRendererButton *c = cellRendererButton(r);
|
||||
gint xpad;
|
||||
PangoLayout *layout;
|
||||
PangoRectangle rect;
|
||||
gint out;
|
||||
|
||||
gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(c), &xpad, NULL);
|
||||
|
||||
layout = gtk_widget_create_pango_layout(widget, c->text);
|
||||
pango_layout_set_width(layout, -1);
|
||||
pango_layout_get_extents(layout, NULL, &rect);
|
||||
g_object_unref(layout);
|
||||
|
||||
out = 2 * xpad + PANGO_PIXELS_CEIL(rect.width);
|
||||
if (minimum != NULL)
|
||||
*minimum = out;
|
||||
if (natural != NULL)
|
||||
*natural = out;
|
||||
}
|
||||
|
||||
// this is based on what GtkCellRendererText does
|
||||
static void cellRendererButton_get_preferred_height_for_width(GtkCellRenderer *r, GtkWidget *widget, gint width, gint *minimum, gint *natural)
|
||||
{
|
||||
cellRendererButton *c = cellRendererButton(r);
|
||||
gint xpad, ypad;
|
||||
PangoLayout *layout;
|
||||
gint height;
|
||||
gint out;
|
||||
|
||||
gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(c), &xpad, &ypad);
|
||||
|
||||
layout = gtk_widget_create_pango_layout(widget, c->text);
|
||||
pango_layout_set_width(layout, ((2 * xpad + width) * PANGO_SCALE));
|
||||
pango_layout_get_pixel_size(layout, NULL, &height);
|
||||
g_object_unref(layout);
|
||||
|
||||
out = 2 * ypad + height;
|
||||
if (minimum != NULL)
|
||||
*minimum = out;
|
||||
if (natural != NULL)
|
||||
*natural = out;
|
||||
}
|
||||
|
||||
// this is basically what GtkCellRendererText does
|
||||
static void cellRendererButton_get_preferred_height(GtkCellRenderer *r, GtkWidget *widget, gint *minimum, gint *natural)
|
||||
{
|
||||
gint width;
|
||||
|
||||
gtk_cell_renderer_get_preferred_width(r, widget, &width, NULL);
|
||||
gtk_cell_renderer_get_preferred_height_for_width(r, widget, width, minimum, natural);
|
||||
}
|
||||
|
||||
// this is based on what GtkCellRendererText does
|
||||
static void cellRendererButton_get_aligned_area(GtkCellRenderer *r, GtkWidget *widget, GtkCellRendererState flags, const GdkRectangle *cell_area, GdkRectangle *aligned_area)
|
||||
{
|
||||
cellRendererButton *c = cellRendererButton(r);
|
||||
gint xpad, ypad;
|
||||
PangoLayout *layout;
|
||||
PangoRectangle rect;
|
||||
gfloat xalign, yalign;
|
||||
gint xoffset, yoffset;
|
||||
|
||||
gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(c), &xpad, &ypad);
|
||||
|
||||
layout = gtk_widget_create_pango_layout(widget, c->text);
|
||||
pango_layout_set_width(layout, -1);
|
||||
pango_layout_get_pixel_extents(layout, NULL, &rect);
|
||||
|
||||
xoffset = 0;
|
||||
yoffset = 0;
|
||||
if (cell_area != NULL) {
|
||||
gtk_cell_renderer_get_alignment(GTK_CELL_RENDERER(c), &xalign, &yalign);
|
||||
xoffset = cell_area->width - (2 * xpad + rect.width);
|
||||
// use explicit casts just to be safe
|
||||
if (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL)
|
||||
xoffset = ((gdouble) xoffset) * (1.0 - xalign);
|
||||
else
|
||||
xoffset *= ((gdouble) xoffset) * xalign;
|
||||
yoffset = yalign * (cell_area->height - (2 * ypad + rect.height));
|
||||
yoffset = MAX(yoffset, 0);
|
||||
}
|
||||
|
||||
aligned_area->x = cell_area->x + xoffset;
|
||||
aligned_area->y = cell_area->y + yoffset;
|
||||
aligned_area->width = 2 * xpad + rect.width;
|
||||
aligned_area->height = 2 * ypad + rect.height;
|
||||
|
||||
g_object_unref(layout);
|
||||
}
|
||||
|
||||
// this is based on both what GtkCellRendererText does and what GtkCellRendererToggle does
|
||||
static void cellRendererButton_render(GtkCellRenderer *r, cairo_t *cr, GtkWidget *widget, const GdkRectangle *background_area, const GdkRectangle *cell_area, GtkCellRendererState flags)
|
||||
{
|
||||
cellRendererButton *c = cellRendererButton(r);
|
||||
gint xpad, ypad;
|
||||
GdkRectangle alignedArea;
|
||||
gint xoffset, yoffset;
|
||||
GtkStyleContext *context;
|
||||
PangoLayout *layout;
|
||||
PangoRectangle rect;
|
||||
|
||||
gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(c), &xpad, &ypad);
|
||||
gtk_cell_renderer_get_aligned_area(GTK_CELL_RENDERER(c), widget, flags, cell_area, &alignedArea);
|
||||
xoffset = alignedArea.x - cell_area->x;
|
||||
yoffset = alignedArea.y - cell_area->y;
|
||||
|
||||
context = setButtonStyle(widget);
|
||||
layout = gtk_widget_create_pango_layout(widget, c->text);
|
||||
|
||||
gtk_render_background(context, cr,
|
||||
background_area->x + xoffset + xpad,
|
||||
background_area->y + yoffset + ypad,
|
||||
background_area->width - 2 * xpad,
|
||||
background_area->height - 2 * ypad);
|
||||
gtk_render_frame(context, cr,
|
||||
background_area->x + xoffset + xpad,
|
||||
background_area->y + yoffset + ypad,
|
||||
background_area->width - 2 * xpad,
|
||||
background_area->height - 2 * ypad);
|
||||
|
||||
pango_layout_set_width(layout, -1);
|
||||
pango_layout_get_pixel_extents(layout, NULL, &rect);
|
||||
xoffset -= rect.x;
|
||||
gtk_render_layout(context, cr,
|
||||
cell_area->x + xoffset + xpad,
|
||||
cell_area->y + yoffset + ypad,
|
||||
layout);
|
||||
|
||||
g_object_unref(layout);
|
||||
unsetButtonStyle(context);
|
||||
}
|
||||
|
||||
static guint clickedSignal;
|
||||
|
||||
static gboolean cellRendererButton_activate(GtkCellRenderer *r, GdkEvent *e, GtkWidget *widget, const gchar *path, const GdkRectangle *background_area, const GdkRectangle *cell_area, GtkCellRendererState flags)
|
||||
{
|
||||
g_signal_emit(r, clickedSignal, 0, path);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GParamSpec *props[2] = { NULL, NULL };
|
||||
|
||||
static void cellRendererButton_set_property(GObject *object, guint prop, const GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
cellRendererButton *c = cellRendererButton(object);
|
||||
|
||||
if (prop != 1) {
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(c, prop, pspec);
|
||||
return;
|
||||
}
|
||||
if (c->text != NULL)
|
||||
g_free(c->text);
|
||||
c->text = g_value_dup_string(value);
|
||||
// GtkCellRendererText doesn't queue a redraw; we won't either
|
||||
}
|
||||
|
||||
static void cellRendererButton_get_property(GObject *object, guint prop, GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
cellRendererButton *c = cellRendererButton(object);
|
||||
|
||||
if (prop != 1) {
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(c, prop, pspec);
|
||||
return;
|
||||
}
|
||||
g_value_set_string(value, c->text);
|
||||
}
|
||||
|
||||
static void cellRendererButton_class_init(cellRendererButtonClass *class)
|
||||
{
|
||||
G_OBJECT_CLASS(class)->dispose = cellRendererButton_dispose;
|
||||
G_OBJECT_CLASS(class)->finalize = cellRendererButton_finalize;
|
||||
G_OBJECT_CLASS(class)->set_property = cellRendererButton_set_property;
|
||||
G_OBJECT_CLASS(class)->get_property = cellRendererButton_get_property;
|
||||
GTK_CELL_RENDERER_CLASS(class)->get_request_mode = cellRendererButton_get_request_mode;
|
||||
GTK_CELL_RENDERER_CLASS(class)->get_preferred_width = cellRendererButton_get_preferred_width;
|
||||
GTK_CELL_RENDERER_CLASS(class)->get_preferred_height_for_width = cellRendererButton_get_preferred_height_for_width;
|
||||
GTK_CELL_RENDERER_CLASS(class)->get_preferred_height = cellRendererButton_get_preferred_height;
|
||||
// don't provide a get_preferred_width_for_height()
|
||||
GTK_CELL_RENDERER_CLASS(class)->get_aligned_area = cellRendererButton_get_aligned_area;
|
||||
// don't provide a get_size()
|
||||
GTK_CELL_RENDERER_CLASS(class)->render = cellRendererButton_render;
|
||||
GTK_CELL_RENDERER_CLASS(class)->activate = cellRendererButton_activate;
|
||||
// don't provide a start_editing()
|
||||
|
||||
props[1] = g_param_spec_string("text",
|
||||
"Text",
|
||||
"Button text",
|
||||
"",
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
|
||||
g_object_class_install_properties(G_OBJECT_CLASS(class), 2, props);
|
||||
|
||||
clickedSignal = g_signal_new("clicked",
|
||||
G_TYPE_FROM_CLASS(class),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
0,
|
||||
NULL, NULL, NULL,
|
||||
G_TYPE_NONE,
|
||||
1, G_TYPE_STRING);
|
||||
}
|
||||
|
||||
GtkCellRenderer *newCellRendererButton(void)
|
||||
{
|
||||
return GTK_CELL_RENDERER(g_object_new(cellRendererButtonType, NULL));
|
||||
}
|
||||
Vendored
+78
@@ -0,0 +1,78 @@
|
||||
// 10 june 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct uiCheckbox {
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget;
|
||||
GtkButton *button;
|
||||
GtkToggleButton *toggleButton;
|
||||
GtkCheckButton *checkButton;
|
||||
void (*onToggled)(uiCheckbox *, void *);
|
||||
void *onToggledData;
|
||||
gulong onToggledSignal;
|
||||
};
|
||||
|
||||
uiUnixControlAllDefaults(uiCheckbox)
|
||||
|
||||
static void onToggled(GtkToggleButton *b, gpointer data)
|
||||
{
|
||||
uiCheckbox *c = uiCheckbox(data);
|
||||
|
||||
(*(c->onToggled))(c, c->onToggledData);
|
||||
}
|
||||
|
||||
static void defaultOnToggled(uiCheckbox *c, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
char *uiCheckboxText(uiCheckbox *c)
|
||||
{
|
||||
return uiUnixStrdupText(gtk_button_get_label(c->button));
|
||||
}
|
||||
|
||||
void uiCheckboxSetText(uiCheckbox *c, const char *text)
|
||||
{
|
||||
gtk_button_set_label(GTK_BUTTON(c->button), text);
|
||||
}
|
||||
|
||||
void uiCheckboxOnToggled(uiCheckbox *c, void (*f)(uiCheckbox *, void *), void *data)
|
||||
{
|
||||
c->onToggled = f;
|
||||
c->onToggledData = data;
|
||||
}
|
||||
|
||||
int uiCheckboxChecked(uiCheckbox *c)
|
||||
{
|
||||
return gtk_toggle_button_get_active(c->toggleButton) != FALSE;
|
||||
}
|
||||
|
||||
void uiCheckboxSetChecked(uiCheckbox *c, int checked)
|
||||
{
|
||||
gboolean active;
|
||||
|
||||
active = FALSE;
|
||||
if (checked)
|
||||
active = TRUE;
|
||||
// we need to inhibit sending of ::toggled because this WILL send a ::toggled otherwise
|
||||
g_signal_handler_block(c->toggleButton, c->onToggledSignal);
|
||||
gtk_toggle_button_set_active(c->toggleButton, active);
|
||||
g_signal_handler_unblock(c->toggleButton, c->onToggledSignal);
|
||||
}
|
||||
|
||||
uiCheckbox *uiNewCheckbox(const char *text)
|
||||
{
|
||||
uiCheckbox *c;
|
||||
|
||||
uiUnixNewControl(uiCheckbox, c);
|
||||
|
||||
c->widget = gtk_check_button_new_with_label(text);
|
||||
c->button = GTK_BUTTON(c->widget);
|
||||
c->toggleButton = GTK_TOGGLE_BUTTON(c->widget);
|
||||
c->checkButton = GTK_CHECK_BUTTON(c->widget);
|
||||
|
||||
c->onToggledSignal = g_signal_connect(c->widget, "toggled", G_CALLBACK(onToggled), c);
|
||||
uiCheckboxOnToggled(c, defaultOnToggled, NULL);
|
||||
|
||||
return c;
|
||||
}
|
||||
Vendored
+120
@@ -0,0 +1,120 @@
|
||||
// 28 august 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
// This file contains helpers for managing child controls.
|
||||
|
||||
struct child {
|
||||
uiControl *c;
|
||||
GtkWidget *widget;
|
||||
|
||||
gboolean oldhexpand;
|
||||
GtkAlign oldhalign;
|
||||
gboolean oldvexpand;
|
||||
GtkAlign oldvalign;
|
||||
|
||||
// Some children can be boxed; that is, they can have an optionally-margined box around them.
|
||||
// uiGroup, uiTab, and uiWindow all do this.
|
||||
GtkWidget *box;
|
||||
|
||||
// If the child is not boxed, this is its parent.
|
||||
// If the child is boxed, this is the box.
|
||||
GtkContainer *parent;
|
||||
|
||||
// This flag is for users of these functions.
|
||||
// For uiBox, this is "spaced".
|
||||
// For uiTab, this is "margined". (uiGroup and uiWindow have to maintain their margined state themselves, since the margined state is independent of whether there is a child for those two.)
|
||||
int flag;
|
||||
};
|
||||
|
||||
struct child *newChild(uiControl *child, uiControl *parent, GtkContainer *parentContainer)
|
||||
{
|
||||
struct child *c = NULL;
|
||||
|
||||
if (child == NULL)
|
||||
return NULL;
|
||||
|
||||
c = uiNew(struct child);
|
||||
c->c = child;
|
||||
c->widget = GTK_WIDGET(uiControlHandle(c->c));
|
||||
|
||||
c->oldhexpand = gtk_widget_get_hexpand(c->widget);
|
||||
c->oldhalign = gtk_widget_get_halign(c->widget);
|
||||
c->oldvexpand = gtk_widget_get_vexpand(c->widget);
|
||||
c->oldvalign = gtk_widget_get_valign(c->widget);
|
||||
|
||||
uiControlSetParent(c->c, parent);
|
||||
uiUnixControlSetContainer(uiUnixControl(c->c), parentContainer, FALSE);
|
||||
c->parent = parentContainer;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
struct child *newChildWithBox(uiControl *child, uiControl *parent, GtkContainer *parentContainer, int margined)
|
||||
{
|
||||
struct child *c = NULL;
|
||||
GtkWidget *box = NULL;
|
||||
|
||||
if (child == NULL)
|
||||
return NULL;
|
||||
box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
gtk_widget_show(box);
|
||||
c = newChild(child, parent, GTK_CONTAINER(box));
|
||||
gtk_widget_set_hexpand(c->widget, TRUE);
|
||||
gtk_widget_set_halign(c->widget, GTK_ALIGN_FILL);
|
||||
gtk_widget_set_vexpand(c->widget, TRUE);
|
||||
gtk_widget_set_valign(c->widget, GTK_ALIGN_FILL);
|
||||
c->box = box;
|
||||
gtk_container_add(parentContainer, c->box);
|
||||
childSetMargined(c, margined);
|
||||
return c;
|
||||
}
|
||||
|
||||
void childRemove(struct child *c)
|
||||
{
|
||||
uiControlSetParent(c->c, NULL);
|
||||
uiUnixControlSetContainer(uiUnixControl(c->c), c->parent, TRUE);
|
||||
|
||||
gtk_widget_set_hexpand(c->widget, c->oldhexpand);
|
||||
gtk_widget_set_halign(c->widget, c->oldhalign);
|
||||
gtk_widget_set_vexpand(c->widget, c->oldvexpand);
|
||||
gtk_widget_set_valign(c->widget, c->oldvalign);
|
||||
|
||||
if (c->box != NULL)
|
||||
gtk_widget_destroy(c->box);
|
||||
|
||||
uiFree(c);
|
||||
}
|
||||
|
||||
void childDestroy(struct child *c)
|
||||
{
|
||||
uiControl *child = NULL;
|
||||
|
||||
child = c->c;
|
||||
childRemove(c);
|
||||
uiControlDestroy(child);
|
||||
}
|
||||
|
||||
GtkWidget *childWidget(struct child *c)
|
||||
{
|
||||
return c->widget;
|
||||
}
|
||||
|
||||
int childFlag(struct child *c)
|
||||
{
|
||||
return c->flag;
|
||||
}
|
||||
|
||||
void childSetFlag(struct child *c, int flag)
|
||||
{
|
||||
c->flag = flag;
|
||||
}
|
||||
|
||||
GtkWidget *childBox(struct child *c)
|
||||
{
|
||||
return c->box;
|
||||
}
|
||||
|
||||
void childSetMargined(struct child *c, int margined)
|
||||
{
|
||||
setMargined(GTK_CONTAINER(c->box), margined);
|
||||
}
|
||||
Vendored
+80
@@ -0,0 +1,80 @@
|
||||
// 15 may 2016
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct uiColorButton {
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget;
|
||||
GtkButton *button;
|
||||
GtkColorButton *cb;
|
||||
GtkColorChooser *cc;
|
||||
void (*onChanged)(uiColorButton *, void *);
|
||||
void *onChangedData;
|
||||
};
|
||||
|
||||
uiUnixControlAllDefaults(uiColorButton)
|
||||
|
||||
static void onColorSet(GtkColorButton *button, gpointer data)
|
||||
{
|
||||
uiColorButton *b = uiColorButton(data);
|
||||
|
||||
(*(b->onChanged))(b, b->onChangedData);
|
||||
}
|
||||
|
||||
static void defaultOnChanged(uiColorButton *b, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
void uiColorButtonColor(uiColorButton *b, double *r, double *g, double *bl, double *a)
|
||||
{
|
||||
GdkRGBA rgba;
|
||||
|
||||
gtk_color_chooser_get_rgba(b->cc, &rgba);
|
||||
*r = rgba.red;
|
||||
*g = rgba.green;
|
||||
*bl = rgba.blue;
|
||||
*a = rgba.alpha;
|
||||
}
|
||||
|
||||
void uiColorButtonSetColor(uiColorButton *b, double r, double g, double bl, double a)
|
||||
{
|
||||
GdkRGBA rgba;
|
||||
|
||||
rgba.red = r;
|
||||
rgba.green = g;
|
||||
rgba.blue = bl;
|
||||
rgba.alpha = a;
|
||||
// no need to inhibit the signal; color-set is documented as only being sent when the user changes the color
|
||||
gtk_color_chooser_set_rgba(b->cc, &rgba);
|
||||
}
|
||||
|
||||
void uiColorButtonOnChanged(uiColorButton *b, void (*f)(uiColorButton *, void *), void *data)
|
||||
{
|
||||
b->onChanged = f;
|
||||
b->onChangedData = data;
|
||||
}
|
||||
|
||||
uiColorButton *uiNewColorButton(void)
|
||||
{
|
||||
uiColorButton *b;
|
||||
GdkRGBA black;
|
||||
|
||||
uiUnixNewControl(uiColorButton, b);
|
||||
|
||||
// I'm not sure what the initial color is; set up a real one
|
||||
black.red = 0.0;
|
||||
black.green = 0.0;
|
||||
black.blue = 0.0;
|
||||
black.alpha = 1.0;
|
||||
b->widget = gtk_color_button_new_with_rgba(&black);
|
||||
b->button = GTK_BUTTON(b->widget);
|
||||
b->cb = GTK_COLOR_BUTTON(b->widget);
|
||||
b->cc = GTK_COLOR_CHOOSER(b->widget);
|
||||
|
||||
gtk_color_chooser_set_use_alpha(b->cc, TRUE);
|
||||
|
||||
g_signal_connect(b->widget, "color-set", G_CALLBACK(onColorSet), b);
|
||||
uiColorButtonOnChanged(b, defaultOnChanged, NULL);
|
||||
|
||||
return b;
|
||||
}
|
||||
Vendored
+66
@@ -0,0 +1,66 @@
|
||||
// 11 june 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct uiCombobox {
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget;
|
||||
GtkComboBox *combobox;
|
||||
GtkComboBoxText *comboboxText;
|
||||
void (*onSelected)(uiCombobox *, void *);
|
||||
void *onSelectedData;
|
||||
gulong onSelectedSignal;
|
||||
};
|
||||
|
||||
uiUnixControlAllDefaults(uiCombobox)
|
||||
|
||||
static void onChanged(GtkComboBox *cbox, gpointer data)
|
||||
{
|
||||
uiCombobox *c = uiCombobox(data);
|
||||
|
||||
(*(c->onSelected))(c, c->onSelectedData);
|
||||
}
|
||||
|
||||
static void defaultOnSelected(uiCombobox *c, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
void uiComboboxAppend(uiCombobox *c, const char *text)
|
||||
{
|
||||
gtk_combo_box_text_append(c->comboboxText, NULL, text);
|
||||
}
|
||||
|
||||
int uiComboboxSelected(uiCombobox *c)
|
||||
{
|
||||
return gtk_combo_box_get_active(c->combobox);
|
||||
}
|
||||
|
||||
void uiComboboxSetSelected(uiCombobox *c, int n)
|
||||
{
|
||||
// we need to inhibit sending of ::changed because this WILL send a ::changed otherwise
|
||||
g_signal_handler_block(c->combobox, c->onSelectedSignal);
|
||||
gtk_combo_box_set_active(c->combobox, n);
|
||||
g_signal_handler_unblock(c->combobox, c->onSelectedSignal);
|
||||
}
|
||||
|
||||
void uiComboboxOnSelected(uiCombobox *c, void (*f)(uiCombobox *c, void *data), void *data)
|
||||
{
|
||||
c->onSelected = f;
|
||||
c->onSelectedData = data;
|
||||
}
|
||||
|
||||
uiCombobox *uiNewCombobox(void)
|
||||
{
|
||||
uiCombobox *c;
|
||||
|
||||
uiUnixNewControl(uiCombobox, c);
|
||||
|
||||
c->widget = gtk_combo_box_text_new();
|
||||
c->combobox = GTK_COMBO_BOX(c->widget);
|
||||
c->comboboxText = GTK_COMBO_BOX_TEXT(c->widget);
|
||||
|
||||
c->onSelectedSignal = g_signal_connect(c->widget, "changed", G_CALLBACK(onChanged), c);
|
||||
uiComboboxOnSelected(c, defaultOnSelected, NULL);
|
||||
|
||||
return c;
|
||||
}
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
// 16 august 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
void uiUnixControlSetContainer(uiUnixControl *c, GtkContainer *container, gboolean remove)
|
||||
{
|
||||
(*(c->SetContainer))(c, container, remove);
|
||||
}
|
||||
|
||||
#define uiUnixControlSignature 0x556E6978
|
||||
|
||||
uiUnixControl *uiUnixAllocControl(size_t n, uint32_t typesig, const char *typenamestr)
|
||||
{
|
||||
return uiUnixControl(uiAllocControl(n, uiUnixControlSignature, typesig, typenamestr));
|
||||
}
|
||||
Loaded 100 of 582 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user