Compare commits
248 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1905191453 | |||
| 8e39eee4af | |||
| da001601e5 | |||
| 665e10fa30 | |||
| 66acf8d4a3 | |||
| 6a88ea55ec | |||
| 3ac556b541 | |||
| 1ec6eb88c9 | |||
| 49666294dc | |||
| 18a86e8aab | |||
| 4f7fa7d50c | |||
| bd31f562fc | |||
| 7b036ccee2 | |||
| 26f38b342b | |||
| a8b7171709 | |||
| ed2b36ef0f | |||
| bb6a166f2a | |||
| 793fd1aa28 | |||
| c63d08c462 | |||
| a677d3f1f0 | |||
| 7ff787f71f | |||
| be51e37231 | |||
| fe6f32c769 | |||
| a512fe6c71 | |||
| 2eec1fb710 | |||
| 546f3628c7 | |||
| ce9d1e6684 | |||
| 05a6d4803e | |||
| e3c0bac9ca | |||
| 5dfbad2399 | |||
| 3b66e51cc6 | |||
| eb62e9ad72 | |||
| 34d078b524 | |||
| b9e91c3119 | |||
| b5ef86d092 | |||
| fb3a964f7f | |||
| 884105a4e2 | |||
| 89f19ccf2e | |||
| bbfc306984 | |||
| 6b8083ab5c | |||
| 2bb907d523 | |||
| d7cab085f7 | |||
| 01c2fa3367 | |||
| 167676f478 | |||
| 8cecb57d93 | |||
| 32a57d5293 | |||
| e7cfa3517f | |||
| 1f66c23919 | |||
| a9642f28bd | |||
| 6a7dd998a6 | |||
| 225f61079d | |||
| 76cef2caa0 | |||
| 96fda0c5e9 | |||
| b99229b48d | |||
| d47b835020 | |||
| f1b6321d3d | |||
| 63b3d05b19 | |||
| 24216e4ab4 | |||
| 6a13245fd9 | |||
| fe9e2bdf49 | |||
| ccca2be496 | |||
| 6839a7e06c | |||
| 7f8b5196a5 | |||
| c046c6af52 | |||
| df18be7bf6 | |||
| 48d1b29537 | |||
| 915f395b5a | |||
| be1476d107 | |||
| 622b648d26 | |||
| d33d72a074 | |||
| 34b92c93b4 | |||
| 81cc3dae65 | |||
| d35ef7ddaf | |||
| 48a333e501 | |||
| 71a959b9a1 | |||
| a00e39f36b | |||
| e328a67d96 | |||
| be951c616a | |||
| 636f162b10 | |||
| 2bd15ac981 | |||
| f8befeea4d | |||
| 9d96d2eb74 | |||
| c3a6390404 | |||
| db63b2c6b1 | |||
| 869c7871f9 | |||
| 3d7be2a2df | |||
| 335624fcbe | |||
| 0f7d5a6506 | |||
| 2619873ca7 | |||
| 24ac28e0d9 | |||
| 5e5c912092 | |||
| 4a690fdeb5 | |||
| 62ebd6592d | |||
| 00299bec0b | |||
| be00c87c6a | |||
| e8bb35811a | |||
| 278e4988e9 | |||
| 78fc1c1e87 | |||
| 8d79f13fd6 | |||
| 7713cd7da9 | |||
| 00aa01109e | |||
| 5e68671828 | |||
| 8367f6b617 | |||
| dd073385c8 | |||
| 80d17d7b33 | |||
| b48c413d2d | |||
| c1c1247af4 | |||
| 5ae9b05223 | |||
| 9afb0d2acd | |||
| c1bf8e891b | |||
| 777e974841 | |||
| 9fc1d90455 | |||
| a390a8908c | |||
| c49a689304 | |||
| 849b267f91 | |||
| 1de4fd78b8 | |||
| 586e81935c | |||
| 972c9b581d | |||
| 6d2594db2d | |||
| 9827a899f1 | |||
| 88d9b65028 | |||
| c98c00bfb9 | |||
| fc3fa32ddd | |||
| cd83c528ee | |||
| 5b0169cd94 | |||
| b411718c94 | |||
| 05232c1d24 | |||
| 0223536ac8 | |||
| f30db0dd47 | |||
| 865c7dabaa | |||
| 0ef2bceeec | |||
| cda2f27a49 | |||
| 10ee643a0c | |||
| 00f5e71666 | |||
| 458c45d2b9 | |||
| c4a3dd3a26 | |||
| 2014c67bfd | |||
| 177e52b0d0 | |||
| 3a0904023a | |||
| c4c9716d61 | |||
| 6208711292 | |||
| 39bd7fb8bc | |||
| 47eb3ff907 | |||
| 21aa70d340 | |||
| bd8c73ecd5 | |||
| 6e65700f38 | |||
| e3d87525d4 | |||
| 3002123ef7 | |||
| ac3fcae649 | |||
| c9b74aebc7 | |||
| f344c989db | |||
| 6c5acd6f23 | |||
| 24bc2c67bc | |||
| 712ce50631 | |||
| 2380c7693c | |||
| 79a2d66880 | |||
| 34e75f8c06 | |||
| 7c6f7d31d2 | |||
| 52f3929a42 | |||
| e09107af98 | |||
| 588a417413 | |||
| 14359a9b7e | |||
| 196a2474fd | |||
| c53b9eed5e | |||
| 92614e4d8b | |||
| d94e430807 | |||
| de64746fdf | |||
| 0531f58a56 | |||
| f516a68cee | |||
| 07857fcd44 | |||
| fbe631b4b4 | |||
| b317951c32 | |||
| 9aa0d58d68 | |||
| 78e2c64da6 | |||
| 16adfd7cc5 | |||
| 1746f7d940 | |||
| 169b5e1dfd | |||
| 87de68028a | |||
| 04843fe714 | |||
| a0c6a3b2cb | |||
| ca691d1294 | |||
| f7985f8c7f | |||
| 4bd827ce5c | |||
| d31b4eab53 | |||
| 1088083e1e | |||
| 601a396aa8 | |||
| a481269022 | |||
| 499f6dc79b | |||
| 740ea0c6be | |||
| 8d4b353e88 | |||
| c0e9565bf6 | |||
| c1a7b4fcdb | |||
| 7d9a8a1363 | |||
| 2e0c6333d3 | |||
| 3a7655a3f0 | |||
| 7b7aac8df4 | |||
| 2e60e4c5b8 | |||
| bca29c537c | |||
| 6d8300f9d9 | |||
| 5a89e9ec7d | |||
| 0db858e814 | |||
| 8cf3881b6b | |||
| 373243d138 | |||
| 3070fac9f5 | |||
| 5c687bd24e | |||
| c880c2b26e | |||
| 089140e1ed | |||
| fb177c03f7 | |||
| 328cac871a | |||
| f0aa59ef0b | |||
| 61556d0a2c | |||
| 2356d165fe | |||
| dbe70eb4f4 | |||
| f86a360f39 | |||
| 7e18c7a316 | |||
| 537acd4765 | |||
| d5bea68058 | |||
| 574a1f23dc | |||
| f8e2c0b70c | |||
| 9a785e0f06 | |||
| bb1745a05d | |||
| 04dff26067 | |||
| fa3362cc66 | |||
| 73bc933eb5 | |||
| 763e9ba8d6 | |||
| 250a0fb3b5 | |||
| 8bc38452b3 | |||
| 1cad13e597 | |||
| 28598f0575 | |||
| f48151b38e | |||
| 39e78ea078 | |||
| 2dcc1b582c | |||
| a374a37971 | |||
| 5427b191c2 | |||
| 3a8a55d177 | |||
| a4974c8df8 | |||
| 97295df516 | |||
| 305df93182 | |||
| d6358944ac | |||
| 59acce1150 | |||
| d4c97e1f4f | |||
| 28b5ee16fb | |||
| 7909306d7a | |||
| 99df8ad38d | |||
| 156c3b3959 | |||
| 94837ed410 | |||
| 4aefe8b42d | |||
| 71080476b1 |
11
.gitignore
vendored
11
.gitignore
vendored
@ -1,5 +1,8 @@
|
||||
microgui
|
||||
*.o
|
||||
test/test
|
||||
**/compile_commands.json
|
||||
**/.cache
|
||||
*.a
|
||||
build/*
|
||||
**/.ccls-cache
|
||||
perf.data*
|
||||
*.rdc
|
||||
test_renderer
|
||||
resources/shaders/compiled/**
|
||||
|
||||
0
.gitmodules
vendored
Normal file
0
.gitmodules
vendored
Normal file
243
LAYOUT
Normal file
243
LAYOUT
Normal file
@ -0,0 +1,243 @@
|
||||
Div Children Alignment
|
||||
+------------------------------------------------+
|
||||
|TOP-LEFT TOP TOP-RIGHT|
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
|LEFT CENTER RIGHT|
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
|BOTTOM-LEFT BOTTOM BOTTOM-RIGHT|
|
||||
+------------------------------------------------+
|
||||
|
||||
ALIGNMENT CHART:
|
||||
+------------------------------+----------------------+------------------------------+-----------------------+------------------------------+----------------------+
|
||||
| TOP-LEFT, ROW: | TOP-LEFT, COLUMN: | BOTTOM, ROW: | BOTTOM, COLUMN: | TOP-RIGHT, ROW: | TOP-RIGHT, COLUMN: |
|
||||
| | | | | | |
|
||||
| +------------------------- - | +----------- - | | +----+ | | - -----------+ |
|
||||
| |+-------++-----++-----+ | |+-------+ | | | E1 | | | +-------+| |
|
||||
| || E1 || E2 || | | || E1 | | | | | | - -----------------------+ | | E1 || |
|
||||
| || |+-----+| E3 | | || | | | +----+ | +-------++-----++-----+| | | || |
|
||||
| |+-------+ | | | |+-------+ | +-------+ +---+ | +------+ | | E1 || E2 || || | +-------+| |
|
||||
| | +-----+ | |+----+ | | E1 |+------+|E3 | | | E2 | | | |+-----+| E3 || | +----+| |
|
||||
| ' | || E2 | | | || E2 || | | | | | +-------+ | || | | E2 || |
|
||||
| ' | |+----+ | +-------++------++---+ | +------+ | +-----+| | +----+| |
|
||||
| | |+---------+ | - ------------------------ - | +--+ | ' | +---------+| |
|
||||
| | || E3 | | | |E3| | ' | | E3 || |
|
||||
| | |+---------+ | | +--+ | | +---------+| |
|
||||
| | ' | | - ----------- - | | ' |
|
||||
| | ' | | | | ' |
|
||||
| | | | | | |
|
||||
+------------------------------+----------------------+------------------------------+-----------------------+------------------------------+----------------------+
|
||||
| LEFT, ROW: | LEFT, COLUMN: | BOTTOM-RIGHT, ROW: | BOTTOM-RIGHT, COLUMN: | TOP, ROW: | TOP, COLUMN: |
|
||||
| | | | | | |
|
||||
| | ' | | ' | | - -------------- - |
|
||||
| ' | |+-------+ | | +-------+| | | +----------+ |
|
||||
| | +----+ | || E1 | | ' | | E1 || | - ----------------------- - | | E1 | |
|
||||
| |+------+ | | | || | | +-----+| | | || | +------++----++-----+ | | | |
|
||||
| || |+-----+| | | |+-------+ | +-------+ | || | +-------+| | | E1 || E2 || E3 | | +----------+ |
|
||||
| || E1 || E2 || E3 | | |+----+ | | E1 |+-----+| E3 || | +----+| | | |+----+| | | +--------+ |
|
||||
| || |+-----+| | | || E2 | | | || E2 || || | | E2 || | +------+ | | | | E2 | |
|
||||
| |+------+ | | | |+----+ | +-------++-----++-----+| | +----+| | +-----+ | +--------+ |
|
||||
| | +----+ | |+---------+ | - -----------------------+ | +---------+| | | +------+ |
|
||||
| ' | || E3 | | | | E3 || | | | E3 | |
|
||||
| ' | |+---------+ | | +---------+| | | | | |
|
||||
| | ' | | - -----------+ | | +------+ |
|
||||
| | ' | | | | |
|
||||
+------------------------------+----------------------+------------------------------+-----------------------+------------------------------+----------------------+
|
||||
| BOTTOM-LEFT, ROW: | BOTTOM-LEFT, COLUMN: | RIGHT, ROW: | RIGHT, COLUMN: | CENTER, ROW: | CENTER, COLUMN: |
|
||||
| | | | | | |
|
||||
| | ' | | ' | | | |
|
||||
| | |+-------+ | | +-------+| | | | +-----------+ |
|
||||
| | || E1 | | ' | | E1 || | | | | E1 | |
|
||||
| ' | || | | +----+| | | || | | +----+ | | | | |
|
||||
| | +-----+ | |+-------+ | +------+ | || | +-------+| | +------+ | | | | +-----------+ |
|
||||
| |+-------+ | | | |+----+ | | |+-----+| || | +----+| | | |+----+| | | +---------+ |
|
||||
| || E1 |+-----+| E3 | | || E2 | | | E1 || E2 || E3 || | | E2 || | ---|--E1--||-E2-||-E3-|--- | ----|---E2----|---- |
|
||||
| || || E2 || | | |+----+ | | |+-----+| || | +----+| | | |+----+| | | +---------+ |
|
||||
| |+-------++-----++-----+ | |+---------+ | +------+ | || | +---------+| | +------+ | | | | +-------+ |
|
||||
| +------------------------- - | || E3 | | +----+| | | E3 || | | +----+ | | E3 | |
|
||||
| | |+---------+ | ' | +---------+| | | | | | | |
|
||||
| | +----------- - | ' | ' | | | +-------+ |
|
||||
| | | | ' | | | |
|
||||
| | | | | | |
|
||||
+------------------------------+----------------------+------------------------------+-----------------------+------------------------------+----------------------+
|
||||
|
||||
div (
|
||||
align: TOP-LEFT | LEFT | BOTTOM-LEFT | BOTTOM | BOTTOM-RIGHT | RIGHT | TOP-RIGHT | RIGHT | CENTER
|
||||
size_x/y: EXACT(x) | GROW() | FIT(min, max)
|
||||
scroll_x/y: true | false
|
||||
resize_x/y: true | false
|
||||
layout: ROW | COLUMN
|
||||
)
|
||||
|
||||
align: alignment of the children elements
|
||||
size: how the div should be sized
|
||||
scroll: enables scrollbars
|
||||
layout: the layout direction of the children
|
||||
|
||||
COLUMN ROW
|
||||
+--------------------+ +----------------------------------------------------+
|
||||
| +----------------+ | |+----------------+ |
|
||||
| | | | || |+------------+ |
|
||||
| | | | || || |+------------------+|
|
||||
| | E1 | | || E1 || E2 || E3 ||
|
||||
| | | | || || |+------------------+|
|
||||
| | | | || |+------------+ |
|
||||
| +----------------+ | |+----------------+ |
|
||||
| +------------+ | +----------------------------------------------------+
|
||||
| | | |
|
||||
| | E2 | |
|
||||
| | | | (both have center alignment)
|
||||
| +------------+ |
|
||||
|+------------------+|
|
||||
|| ||
|
||||
|| E3 ||
|
||||
|| ||
|
||||
|| ||
|
||||
|+------------------+|
|
||||
+--------------------+
|
||||
|
||||
Element {
|
||||
id: uint
|
||||
sizing: { min_w, min_h max_w, max_h }
|
||||
bounds: { x, y, w, h }
|
||||
}
|
||||
|
||||
id: unique identifier of the element
|
||||
sizing: the size that the element wants
|
||||
bounds: the absoulte bounds that the element got assigned
|
||||
|
||||
Rendering
|
||||
=========
|
||||
|
||||
Rendering happens when the element is called (immediately for leaf widgets like buttons and at the end
|
||||
for root widgets like divs). The drawing is done on the bounds assigned to the widget, these bounds
|
||||
have a one-frame delay on the current layout.
|
||||
|
||||
The layout is calculated by each div at the end of their block and at frame end all the sizes and positions
|
||||
are assigned at frame end by iterating the element tree.
|
||||
|
||||
ElemDiv {
|
||||
align: TOP-LEFT | LEFT | BOTTOM-LEFT | BOTTOM | BOTTOM-RIGHT | RIGHT | TOP-RIGHT | RIGHT | CENTER
|
||||
size_x/y: { min, max }
|
||||
scroll_x/y: true | false
|
||||
layout: ROW | COLUMN
|
||||
children_size_x/y: { min, max }
|
||||
}
|
||||
|
||||
size:
|
||||
- min != max -> FIT sizing, fit to the content but respect the min and max size
|
||||
- min == max == 0 -> GROW sizing, grow to the max amount of space possible
|
||||
- min == max != 0 -> EXACT sizing
|
||||
children_size: the size of the combined children sizes
|
||||
|
||||
|
||||
|
||||
root(size_x: screen width, size_y: screen height, layout: ROW) {
|
||||
|
||||
div1(layout: COLUMN, size_x: FIT, size_y GROW, resize_x: true) {
|
||||
E1()
|
||||
E2()
|
||||
E3()
|
||||
E4()
|
||||
} <-(end div 1)
|
||||
|
||||
div2(size_x: GROW, size_y: GROW) {
|
||||
...
|
||||
} <-(end div 2)
|
||||
|
||||
div3(layout: COLUMN, size_x: FIT, size_y: GROW) {
|
||||
E5()
|
||||
E6()
|
||||
E7()
|
||||
} <-(end div 3)
|
||||
|
||||
} <-(end root)
|
||||
(frame end)
|
||||
|
||||
|
||||
+-Root-Div------------------------------------------------+
|
||||
|+-Div-1----------++-Div-2--------------------++-Div-3---+|
|
||||
||+--------------+|| ||+-------+||
|
||||
||| E1 ||| ||| E5 |||
|
||||
||| ||| ||| |||
|
||||
||+--------------+|| ||+-------+|| [Root Div]
|
||||
||+--------------+|| ||+-------+|| |
|
||||
||| E2 ||| ||| E6 ||| +----------+----+-------+
|
||||
||| ||| ||| ||| v v v
|
||||
||+--------------+|| ||+-------+|| [Div 1] [Div 2] [Div 3]
|
||||
||+------+ || ||+-------+|| | |
|
||||
||| | || ||| E7 ||| +----+----+----+ |
|
||||
||| E3 | || ||| ||| v v v v |
|
||||
||| | || ||+-------+|| [E1] [E2] [E3] [E4] +----+----+
|
||||
||+------+ || || || v v v
|
||||
||+------+ || || || [E5] [E6] [E7]
|
||||
||| | || || ||
|
||||
||| E4 | || || ||
|
||||
||| | || || ||
|
||||
||+------+ || || ||
|
||||
|| || || ||
|
||||
|+----------------++--------------------------++---------+|
|
||||
+---------------------------------------------------------+
|
||||
|
||||
the call order is as follows
|
||||
|
||||
E1() -> updates the children size of div1
|
||||
E2() -> " "
|
||||
E3() -> " "
|
||||
E4() -> " "
|
||||
end div1() -> updates the children size of root
|
||||
end div2() -> updates the children size of root
|
||||
E5() -> updates the children size of div3
|
||||
E6() -> " "
|
||||
E7() -> " "
|
||||
end root() -> does nothing
|
||||
|
||||
at frame end:
|
||||
* Root: the root has a size constraint of fit so the bounds get assigned the whole window
|
||||
* Div 1: the width has a size of fit, so it gets set to the children bounds, the height is set
|
||||
to the root height since it has a height of GROW
|
||||
- E1 to E4 get laid out
|
||||
* Div 2: it has a width of GROW which is **along** the layout axis, so it gets added to grow list
|
||||
the height gets set to the root height
|
||||
* Div 3: the width is FIT, so it gets set to the content width, the height gets se to the root
|
||||
height.
|
||||
- E5 to E7 get laid out
|
||||
* Div 2: is given a width (if there were other contending grow divs along the layout axis they
|
||||
would also get sized).
|
||||
- Now that div 2 has a size all it's children can be given a size
|
||||
|
||||
Styling
|
||||
=======
|
||||
|
||||
The element bounds include the whole CSS box model:
|
||||
|
||||
+---------------------------------------------+
|
||||
| MARGIN |
|
||||
| +-----------------------------------+ |
|
||||
| |xxxxxxxxxxx BORDER xxxxxxxxxxxx| |
|
||||
| |x+-------------------------------+x| |
|
||||
| |x| PADDING |x| |
|
||||
| |x| +-----------------------+ |x| |
|
||||
| |x| | | |x| |
|
||||
| |x| | CONTENT | |x| |
|
||||
| |x| | | |x| |
|
||||
| |x| +-----------------------+ |x| |
|
||||
| |x| |x| |
|
||||
| |x+-------------------------------+x| |
|
||||
| |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| |
|
||||
| +-----------------------------------+ |
|
||||
| |
|
||||
+---------------------------------------------+
|
||||
|
||||
Styling happens via a .css file, the sizing strictly refers to the content, so if the the user
|
||||
requests an exact size of 100px*100px the content box will have those dimensions, but the element
|
||||
bounds will be larger.
|
||||
165
LICENSE
Normal file
165
LICENSE
Normal file
@ -0,0 +1,165 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
4
RENDERER
4
RENDERER
@ -1,4 +0,0 @@
|
||||
Divide up the OpenGL renderer into three parts, one part that draws simple shapes,
|
||||
another that only draws text and one that is responsible for drawing icons and
|
||||
mapping sprites in general, this way all the texture atlases are separate and
|
||||
everything is done within 3 draw calls
|
||||
136
TODO
Normal file
136
TODO
Normal file
@ -0,0 +1,136 @@
|
||||
# TODOs, semi-random sorting
|
||||
|
||||
[x] Implement glyph draw command
|
||||
[x] Implement div.view and scrollbars
|
||||
[x] Port font system from C to C3 (rewrite1)
|
||||
[ ] Update ARCHITECTURE.md
|
||||
[x] Write a README.md
|
||||
[x] Use an arena allocator for cache
|
||||
[ ] Do not redraw if there was no update (no layout and no draw)
|
||||
[ ] Do command buffer damage tracking based on a context grid (see rxi writeup)
|
||||
[x] Better handling of the active and focused widgets, try to maintain focus until mouse release (fix scroll bars)
|
||||
[x] Clip element bounds to parent div, specifically text
|
||||
[ ] Resizeable divs
|
||||
[x] Implement a z index and sort command buffer based on that
|
||||
[ ] Ctx.set_z_index()
|
||||
[x] Sort command buffer on insertion
|
||||
[x] Standardize element handling, for example all buttons do almost the same thing, so write a lot
|
||||
of boiler plate and reuse it
|
||||
[x] The id combination in gen_id() uses an intger division, which is costly, use another combination
|
||||
function that is non-linear and doesn't use division
|
||||
[ ] Animations, somehow
|
||||
[x] Maybe cache codepoint converted strings
|
||||
[x] Fix scroll wheel when div is scrolled
|
||||
[ ] Be consistent with the initialization methods some are foo.new() and some are foo.init()
|
||||
[ ] Implement image loading (.bmp, .ff, .qoi and .png), in the future even lossy images like .jpg
|
||||
[x] .qoi
|
||||
[ ] .ff
|
||||
[ ] .bmp
|
||||
[ ] .png
|
||||
[ ] .jpg
|
||||
[ ] gif support?
|
||||
[x] layout_set_max_rows() and layout_set_max_columns()
|
||||
[x] Maybe SDF sprites??
|
||||
[x] Stylesheets and stylesheet import
|
||||
[x] use SDF to draw anti-aliased rounded rectangles https://zed.dev/blog/videogame
|
||||
[ ] Subdivide modules into ugui::ug for exported functions and ugui::core for
|
||||
internal use functions (used to create widgets)
|
||||
[x] The render loop RAPES the gpu, valve pls fix
|
||||
[x] The way the element structures are implemented wastes a lot of memory since
|
||||
each struct Elem, struct Cmd, etc. is as big as the largest element. It would
|
||||
be better to use a different allcation strategy.
|
||||
[ ] Add a way to handle time events like double clicks
|
||||
[x] Fix how padding is applied in push_rect. In CSS padding is applied between the border and the
|
||||
content, the background color is applied starting from the border. Right now push_rect() offsets
|
||||
the background rect by both border and padding
|
||||
[x] Investigate why the debug pointer (cyan rectangle) disappears...
|
||||
|
||||
## Layout
|
||||
|
||||
[x] Flexbox
|
||||
[x] Center elements to the row/column
|
||||
[x] Text wrapping / reflow
|
||||
[x] Implement a better and unified way to place a glyph and get the cursor position, maybe with a struct
|
||||
[x] Correct whitespace handling in text (\t \r etc)
|
||||
[x] Consider a multi-pass recursive approach to layout (like https://github.com/nicbarker/clay)
|
||||
instead of the curren multi-frame approach.
|
||||
[x] Implement column/row sizing (min, max)
|
||||
[x] Implement a way to size the element as the current row/column size
|
||||
* +-------------+
|
||||
* | |
|
||||
* +-------------+
|
||||
* +--+
|
||||
* | |
|
||||
* +--+
|
||||
* <------------->
|
||||
* column size
|
||||
See the calculator example for why it is useful
|
||||
[ ] Find a way to concile pixel measurements to the mm ones used in css, for example in min/max sizing
|
||||
of elements
|
||||
[x] Center elements to div (center children_bounds to the center of the div bounds and shift the origin accordingly)
|
||||
[x] Use containing_rect() in position_element() to skip some computing and semplify the function
|
||||
[x] Rename position_element() to layout_element()
|
||||
[x] Make functions to mark rows/columns as full, to fix the calculator demo
|
||||
|
||||
## Input
|
||||
|
||||
[x] Keyboard input
|
||||
[x] Mouse scroll wheel
|
||||
[ ] Touch input
|
||||
[x] Do not set input event to true if the movement was zero (like no mouse movement)
|
||||
[x] Use input event flags, for example to consume the input event
|
||||
[x] Fix bug in text box: when spamming keys you can get multiple characters in the text input field
|
||||
of the context, this causes a bug where only the first char is actually used
|
||||
|
||||
## Commands
|
||||
|
||||
[x] rect commads should have:
|
||||
- border width
|
||||
- border radius
|
||||
[x] add a command to update an atlas
|
||||
[x] New window command, useful for popups
|
||||
[x] Text command returns the text bounds, this way we can avoid the pattern
|
||||
draw_text(a, pos) -> off = compute_bounds(a) -> draw_text(b, pos+off) -> ...
|
||||
[ ] Rounded rectangle with different radius for each corner
|
||||
|
||||
## Atlas
|
||||
|
||||
[ ] Add an interface to create, destroy, update and get atlases based on their ids
|
||||
[ ] Implement multiple font atlases
|
||||
[ ] Pixel format conversion
|
||||
|
||||
## Fonts
|
||||
|
||||
[x] Fix the missing alpha channel
|
||||
[x] Fix the alignment
|
||||
|
||||
## Widgets
|
||||
|
||||
[x] Dynamic text box to implement an fps counter
|
||||
[x] Button with label
|
||||
[x] Text Input box
|
||||
[ ] Icon Buttons
|
||||
[x] Switch
|
||||
[x] Checkbox
|
||||
[ ] Selectable text box
|
||||
|
||||
## API
|
||||
|
||||
[ ] Introduce a Layout structure that specifies the positioning of elements inside
|
||||
a Div element. This would allow specifying alignment, maximum and minimum sizing
|
||||
margins between children, etc.
|
||||
This is different from style which is applied per-element.
|
||||
[ ] Remove Ids for element that don't need them. Such elements are button, toggles,
|
||||
and all elements which do not have internal data that has to be cached and/or
|
||||
queried by the user for later use. This allows for smaller caches and in general
|
||||
reduces some load, since most of the stuff is recomputed for every frame.
|
||||
|
||||
## SDL3 Renderer
|
||||
|
||||
[x] smart batching
|
||||
[x] maybe use instancing since we are always drawing the same geometry. With instancing every
|
||||
different quad could have its coulour, border and radius with much better performance than
|
||||
issuing a draw call for every quad (and uploading it)
|
||||
https://rastertek.com/dx11win10tut48.html
|
||||
https://www.braynzarsoft.net/viewtutorial/q16390-33-instancing-with-indexed-primitives
|
||||
[ ] implement min and max fps
|
||||
40
def_style.h
40
def_style.h
@ -1,40 +0,0 @@
|
||||
#ifndef _UG_DEF_STYLE_H
|
||||
#define _UG_DEF_STYLE_H
|
||||
|
||||
#include "ugui.h"
|
||||
|
||||
#define SZ_INT(x) x.size.i
|
||||
|
||||
static const ug_style_t default_style = {
|
||||
.color = {
|
||||
.bg = RGB_FORMAT(0x131313),
|
||||
.fg = RGB_FORMAT(0xffffff),
|
||||
},
|
||||
.margin = SIZE_PX(3),
|
||||
.border = {
|
||||
.color = RGB_FORMAT(0xf50a00),
|
||||
.size = SIZE_PX(2),
|
||||
},
|
||||
.title = {
|
||||
.color = {
|
||||
.bg = RGB_FORMAT(0xbbbbbb),
|
||||
.fg = RGB_FORMAT(0xffff00),
|
||||
},
|
||||
.height = SIZE_PX(20),
|
||||
.font_size = SIZE_PX(14),
|
||||
},
|
||||
.btn = {
|
||||
.color = {
|
||||
.act = RGB_FORMAT(0x440044),
|
||||
.bg = RGB_FORMAT(0x006600),
|
||||
.fg = RGB_FORMAT(0xffff00),
|
||||
.sel = RGB_FORMAT(0x0a0aff),
|
||||
.br = RGB_FORMAT(0xff00ff),
|
||||
},
|
||||
.font_size = SIZE_PX(10),
|
||||
.border = SIZE_PX(5),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
3
font-to-atlas/.gitignore
vendored
3
font-to-atlas/.gitignore
vendored
@ -1,3 +0,0 @@
|
||||
*.ff
|
||||
*.png
|
||||
font-to-atlas
|
||||
@ -1,2 +0,0 @@
|
||||
font-to-atlas: main.c ff.c ff.h
|
||||
cc -lm -g main.c ff.c -o font-to-atlas
|
||||
@ -1,118 +0,0 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "ff.h"
|
||||
|
||||
|
||||
#define MAX(a,b) a>b?a:b
|
||||
|
||||
|
||||
struct ff * ff_new(uint32_t width, uint32_t height)
|
||||
{
|
||||
uint64_t size = (uint64_t)width*height*sizeof(struct ff);
|
||||
struct ff *image = malloc(sizeof(struct ff) + size);
|
||||
if (!image)
|
||||
return NULL;
|
||||
memcpy(image->magic, "farbfeld", 8);
|
||||
image->width = htonl(width);
|
||||
image->height = htonl(height);
|
||||
|
||||
// create a transparent image
|
||||
memset(image->pixels, 0, size);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
|
||||
uint64_t ff_bytes(const struct ff *image)
|
||||
{
|
||||
if (!image)
|
||||
return 0;
|
||||
return sizeof(struct ff)+(uint64_t)ntohl(image->width)*ntohl(image->height)*sizeof(struct ff_px);
|
||||
}
|
||||
|
||||
|
||||
struct ff * ff_resize(struct ff *image, uint32_t width, uint32_t height)
|
||||
{
|
||||
struct ff *new = NULL;
|
||||
int64_t size = sizeof(struct ff)+(int64_t)width*height*sizeof(uint64_t);
|
||||
if (image) {
|
||||
int64_t old_size = ff_bytes(image);
|
||||
if (old_size == size)
|
||||
return image;
|
||||
|
||||
new = realloc(image, size);
|
||||
if (!new)
|
||||
return NULL;
|
||||
|
||||
uint32_t old_width = ntohl(new->width);
|
||||
uint32_t old_height = ntohl(new->height);
|
||||
if (size-old_size > 0) {
|
||||
struct ff_px *b = new->pixels;
|
||||
memset(&b[old_width*old_height], 0, size-old_size);
|
||||
for (int64_t c = (int64_t)old_height-1; c >= 0; c--) {
|
||||
memmove(&b[width*c], &b[old_width*c], sizeof(struct ff_px)*old_width);
|
||||
memset(&b[c*old_width], 0, sizeof(struct ff_px)*(c*width-c*old_width));
|
||||
}
|
||||
} else {
|
||||
}
|
||||
new->height = htonl(height);
|
||||
new->width = htonl(width);
|
||||
|
||||
} else {
|
||||
new = ff_new(width, height);
|
||||
}
|
||||
|
||||
return new;
|
||||
}
|
||||
|
||||
|
||||
int ff_verify (const struct ff *image)
|
||||
{
|
||||
if (!image || strncmp("farbfeld", (char*)image->magic, 8))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int ff_free(struct ff *image)
|
||||
{
|
||||
if (!ff_verify(image))
|
||||
free(image);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// overlays the bitmap containing only 1 8bpp channel to the image starting at (x,y)
|
||||
// be stands for the data is already big endian
|
||||
int ff_overlay_8r(struct ff **image, const uint8_t *bitmap, uint32_t x, uint32_t y, uint32_t w, uint32_t h)
|
||||
{
|
||||
if (!image || !*image)
|
||||
return 1;
|
||||
|
||||
uint32_t iw = ntohl((*image)->width), ih = ntohl((*image)->height);
|
||||
*image = ff_resize(*image, MAX(iw, x+w), MAX(ih, y+h));
|
||||
if (!image)
|
||||
return -1;
|
||||
iw = ntohl((*image)->width);
|
||||
ih = ntohl((*image)->height);
|
||||
|
||||
for (uint32_t r = 0; r < h; r++) {
|
||||
for (uint32_t c = 0; c < w; c++) {
|
||||
uint8_t col = bitmap[r*w+c];
|
||||
struct ff_px p = {
|
||||
.r = 0xffff,
|
||||
.g = 0xffff,
|
||||
.b = 0xffff,
|
||||
.a = htons(257*col)
|
||||
};
|
||||
(*image)->pixels[(r+y)*iw + (c+x)] = p;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
#ifndef _FARBFELD_EZ_H
|
||||
#define _FARBFELD_EZ_H
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
struct __attribute__((packed)) ff_px { uint16_t r, g, b, a; };
|
||||
|
||||
struct __attribute__((packed)) ff {
|
||||
int8_t magic[8];
|
||||
uint32_t width, height;
|
||||
struct ff_px pixels[];
|
||||
};
|
||||
|
||||
|
||||
struct ff * ff_new(uint32_t width, uint32_t height);
|
||||
int ff_verify (const struct ff *image);
|
||||
int ff_free(struct ff *image);
|
||||
uint64_t ff_bytes(const struct ff *image);
|
||||
struct ff * ff_resize(struct ff *image, uint32_t width, uint32_t height);
|
||||
int ff_overlay_8r(struct ff **image, const uint8_t *bitmap, uint32_t x, uint32_t y, uint32_t w, uint32_t h);
|
||||
|
||||
|
||||
#endif
|
||||
@ -1,107 +0,0 @@
|
||||
#define _POSIX_C_SOURCE 200809l
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#define STBTT_STATIC
|
||||
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <err.h>
|
||||
|
||||
#include "stb_truetype.h"
|
||||
#include "ff.h"
|
||||
|
||||
|
||||
const int font_size = 32;
|
||||
|
||||
|
||||
void map_file(const unsigned char **str, int *size, const char *path)
|
||||
{
|
||||
if (!path)
|
||||
err(EXIT_FAILURE, "NULL filename");
|
||||
FILE *fp = fopen(path, "r");
|
||||
if (!fp)
|
||||
err(EXIT_FAILURE, "Cannot open file %s", path);
|
||||
*size = lseek(fileno(fp), 0, SEEK_END);
|
||||
if (*size == (off_t)-1)
|
||||
err(EXIT_FAILURE, "lseek failed");
|
||||
*str = mmap(0, *size, PROT_READ, MAP_PRIVATE, fileno(fp), 0);
|
||||
if (*str == (void*)-1)
|
||||
err(EXIT_FAILURE, "mmap failed");
|
||||
if (fclose(fp))
|
||||
err(EXIT_FAILURE, "Error closing file");
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (argc < 2)
|
||||
return EXIT_FAILURE;
|
||||
int len;
|
||||
const unsigned char *map;
|
||||
map_file(&map, &len, argv[1]);
|
||||
|
||||
stbtt_fontinfo font;
|
||||
stbtt_InitFont(&font, map, stbtt_GetFontOffsetForIndex(map, 0));
|
||||
|
||||
// all this is to get the font bounding box in pixels
|
||||
float font_scale = 1.0;
|
||||
font_scale = stbtt_ScaleForPixelHeight(&font, font_size);
|
||||
int ascent, descent, linegap;
|
||||
stbtt_GetFontVMetrics(&font, &ascent, &descent, &linegap);
|
||||
int x0,y0,x1,y1;
|
||||
int bound_w, bound_h;
|
||||
stbtt_GetFontBoundingBox(&font, &x0, &y0, &x1, &y1);
|
||||
|
||||
printf("font_scale: %f\n", font_scale);
|
||||
printf("x0:%d y0:%d x1:%d y1:%d\n",x0,y0,x1,y1);
|
||||
|
||||
int baseline = font_scale * -y0;
|
||||
bound_h = (baseline+font_scale*y1) - (baseline+font_scale*y0);
|
||||
bound_w = (font_scale*x1) - (font_scale*x0);
|
||||
baseline = bound_h - baseline;
|
||||
unsigned char *bitmap = malloc(bound_h*bound_w);
|
||||
if (!bitmap)
|
||||
err(EXIT_FAILURE, "Cannot allocate bitmap");
|
||||
|
||||
printf("bounding h:%d w:%d\n", bound_h, bound_w);
|
||||
printf("baseline: %d\n", baseline);
|
||||
|
||||
struct ff *image = ff_new(0, 0);
|
||||
|
||||
// get all ascii
|
||||
int x = 0, y = 0, maxwidth = 64*bound_w;
|
||||
for (unsigned int i = 0; i <= 0x7F; i++) {
|
||||
int x0,y0,x1,y1,w,h,l,a, ox,oy;
|
||||
int g = stbtt_FindGlyphIndex(&font, i);
|
||||
|
||||
stbtt_GetGlyphBitmapBoxSubpixel(&font, g, font_scale, font_scale, 0, 0, &x0, &y0, &x1, &y1);
|
||||
w = x1 - x0;
|
||||
h = y1 - y0;
|
||||
//printf("%d\n", y0);
|
||||
stbtt_GetGlyphHMetrics(&font, g, &a, &l);
|
||||
stbtt_MakeGlyphBitmapSubpixel(&font, bitmap, w, h, w, font_scale, font_scale, 0, 0, g);
|
||||
//printf("'%c' -> l*scale:%.0f, y0:%d\n", i, font_scale*l, bound_h+y0);
|
||||
ox = font_scale*l;
|
||||
oy = bound_h+y0;
|
||||
ff_overlay_8r(&image, bitmap, x+ox, y+oy, w, h);
|
||||
|
||||
x += bound_w;
|
||||
if (x >= maxwidth) y += bound_h;
|
||||
x %= maxwidth;
|
||||
|
||||
}
|
||||
|
||||
FILE *fp = fopen("out.ff", "w");
|
||||
if (fp) {
|
||||
fwrite(image, 1, ff_bytes(image), fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
free(bitmap);
|
||||
ff_free(image);
|
||||
munmap((void *)map, len);
|
||||
return 0;
|
||||
}
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
40
input.c
40
input.c
@ -1,40 +0,0 @@
|
||||
#include "ugui.h"
|
||||
|
||||
/*=============================================================================*
|
||||
* Input Handling *
|
||||
*=============================================================================*/
|
||||
|
||||
|
||||
#define TEST_CTX(ctx) { if (!ctx) return -1; }
|
||||
|
||||
|
||||
int ug_input_mousemove(ug_ctx_t *ctx, int x, int y)
|
||||
{
|
||||
TEST_CTX(ctx)
|
||||
if (x < 0 || y < 0)
|
||||
return 0;
|
||||
|
||||
ctx->mouse.pos = (ug_vec2_t){.x = x, .y = y};
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int ug_input_mousedown(ug_ctx_t *ctx, unsigned int mask)
|
||||
{
|
||||
TEST_CTX(ctx);
|
||||
|
||||
ctx->mouse.update |= mask;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int ug_input_mouseup(ug_ctx_t *ctx, unsigned int mask)
|
||||
{
|
||||
TEST_CTX(ctx);
|
||||
|
||||
ctx->mouse.update |= mask;
|
||||
|
||||
return 0;
|
||||
}
|
||||
0
linux-x64/.gitkeep
Normal file
0
linux-x64/.gitkeep
Normal file
11
manifest.json
Normal file
11
manifest.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"provides" : "ugui",
|
||||
"sources" : [ "src/**" ],
|
||||
"targets" : {
|
||||
"linux-x64" : {
|
||||
"link-args" : [],
|
||||
"dependencies" : ["schrift"],
|
||||
"linked-libraries" : []
|
||||
}
|
||||
}
|
||||
}
|
||||
2
opengl-ren/.gitignore
vendored
2
opengl-ren/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
a.out
|
||||
gl
|
||||
@ -1,5 +0,0 @@
|
||||
CFLAGS = -Wall -Wextra -Wpedantic -std=c11 -g
|
||||
LDFLAGS = -lSDL2 -lGLEW -lGL
|
||||
|
||||
gl: main.c
|
||||
gcc ${CFLAGS} ${LDFLAGS} main.c -o gl
|
||||
@ -1,2 +0,0 @@
|
||||
since I've never worked with opengl I made this folder as a little test environment
|
||||
for an SDL-OpenGL renderer
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 862 B |
@ -1,12 +0,0 @@
|
||||
#version 330
|
||||
|
||||
flat in vec4 out_color;
|
||||
in vec2 texture_coord;
|
||||
uniform sampler2D texture_sampler;
|
||||
|
||||
out vec4 color;
|
||||
|
||||
void main()
|
||||
{
|
||||
color = texture(texture_sampler, texture_coord) + out_color;
|
||||
}
|
||||
@ -1,521 +0,0 @@
|
||||
#define _POSIX_C_SOURCE 200809l
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <err.h>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <GL/glew.h>
|
||||
#include <SDL2/SDL_opengl.h>
|
||||
|
||||
|
||||
#define GLSL_VERT_SHADER "vertex.glsl"
|
||||
#define GLSL_FRAG_SHADER "fragment.glsl"
|
||||
#define PACKED __attribute__((packed))
|
||||
|
||||
|
||||
const int vertindex = 0;
|
||||
const int colindex = 1;
|
||||
const int textindex = 2;
|
||||
|
||||
struct {
|
||||
SDL_Window *w;
|
||||
SDL_GLContext *gl;
|
||||
GLuint gl_vertbuffer;
|
||||
GLuint gl_program;
|
||||
GLuint font_texture;
|
||||
} ren = {0};
|
||||
|
||||
typedef struct PACKED {
|
||||
union { GLfloat x, u; };
|
||||
union { GLfloat y, v; };
|
||||
} vec2;
|
||||
|
||||
typedef struct PACKED {
|
||||
union { GLfloat x, r; };
|
||||
union { GLfloat y, g; };
|
||||
union { GLfloat z, b; };
|
||||
union { GLfloat w, a; };
|
||||
} vec4;
|
||||
|
||||
// a vertex has a position and a color
|
||||
struct PACKED vertex {
|
||||
vec2 pos;
|
||||
vec2 texture;
|
||||
vec4 color;
|
||||
};
|
||||
|
||||
|
||||
int w_height(SDL_Window *);
|
||||
int w_width(SDL_Window *);
|
||||
|
||||
|
||||
// this just descrives a farbfeld image
|
||||
// https://tools.suckless.org/farbfeld/
|
||||
struct PACKED _ff {
|
||||
uint8_t magic[8];
|
||||
uint32_t w, h;
|
||||
uint64_t bytes[];
|
||||
};
|
||||
const int gw = 7, gh = 9;
|
||||
unsigned int fw, fh;
|
||||
|
||||
|
||||
struct {
|
||||
struct vertex *v;
|
||||
int size, idx;
|
||||
int prev_idx;
|
||||
unsigned long int hash, prev_hash;
|
||||
} vstack = {0};
|
||||
|
||||
|
||||
void grow_stack(int step)
|
||||
{
|
||||
vstack.v = realloc(vstack.v, (vstack.size+step)*sizeof(*(vstack.v)));
|
||||
if(!vstack.v)
|
||||
err(-1, "Could not allocate stack #S: %s", strerror(errno));
|
||||
memset(&(vstack.v[vstack.size]), 0, step*sizeof(*(vstack.v)));
|
||||
vstack.size += step;
|
||||
}
|
||||
|
||||
void push(struct vertex v)
|
||||
{
|
||||
if (vstack.idx >= vstack.size)
|
||||
grow_stack(6);
|
||||
vstack.v[vstack.idx++] = v;
|
||||
}
|
||||
|
||||
|
||||
void update_hash()
|
||||
{
|
||||
if (!vstack.idx)
|
||||
return;
|
||||
unsigned int hash = 0x5400F1B3;
|
||||
unsigned char *v = (unsigned char *)vstack.v;
|
||||
int size = vstack.idx;
|
||||
|
||||
for (; size; size--) {
|
||||
hash += v[size-1];
|
||||
hash += hash << 10;
|
||||
hash ^= hash >> 6;
|
||||
}
|
||||
hash += hash << 3;
|
||||
hash ^= hash >> 11;
|
||||
hash += hash << 15;
|
||||
|
||||
vstack.hash = hash;
|
||||
}
|
||||
|
||||
|
||||
void force_changed()
|
||||
{
|
||||
vstack.prev_idx = 0;
|
||||
}
|
||||
|
||||
|
||||
int changed()
|
||||
{
|
||||
return vstack.prev_idx != vstack.idx || vstack.prev_hash != vstack.hash;
|
||||
}
|
||||
|
||||
|
||||
int vstack_push_quad_c(int x, int y, int w, int h, vec4 color)
|
||||
{
|
||||
// x4,y4 x3,y3
|
||||
// +-------------+
|
||||
// |(x,y) /|
|
||||
// | / |
|
||||
// | 2 / |
|
||||
// | / |
|
||||
// | / |
|
||||
// | / 1 |
|
||||
// |/ |
|
||||
// +-------------+
|
||||
// x1,y1 x2,y2
|
||||
|
||||
int hw = w_width(ren.w)/2;
|
||||
int hh = w_height(ren.w)/2;
|
||||
|
||||
float x1, x2, x3, x4;
|
||||
float y1, y2, y3, y4;
|
||||
|
||||
x4 = x1 = (float)(x - hw) / hw;
|
||||
x2 = x3 = (float)(x+w - hw) / hw;
|
||||
y4 = y3 = (float)(hh - y) / hh;
|
||||
y1 = y2 = (float)(hh - y-h) / hh;
|
||||
|
||||
push((struct vertex){ .pos.x=x1, .pos.y=y1, .color=color });
|
||||
push((struct vertex){ .pos.x=x2, .pos.y=y2, .color=color });
|
||||
push((struct vertex){ .pos.x=x3, .pos.y=y3, .color=color });
|
||||
push((struct vertex){ .pos.x=x1, .pos.y=y1, .color=color });
|
||||
push((struct vertex){ .pos.x=x3, .pos.y=y3, .color=color });
|
||||
push((struct vertex){ .pos.x=x4, .pos.y=y4, .color=color });
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int vstack_push_quad_t(int x, int y, int w, int h, int u, int v)
|
||||
{
|
||||
// x4,y4 x3,y3
|
||||
// +-------------+
|
||||
// |(x,y) /|
|
||||
// | / |
|
||||
// | 2 / |
|
||||
// | / |
|
||||
// | / |
|
||||
// | / 1 |
|
||||
// |/ |
|
||||
// +-------------+
|
||||
// x1,y1 x2,y2
|
||||
|
||||
int hw = w_width(ren.w)/2;
|
||||
int hh = w_height(ren.w)/2;
|
||||
|
||||
float x1, x2, x3, x4;
|
||||
float y1, y2, y3, y4;
|
||||
|
||||
x1 = x4 = (float)(x - hw) / hw;
|
||||
x2 = x3 = (float)(x+w - hw) / hw;
|
||||
y1 = y2 = (float)(hh - y-h) / hh;
|
||||
y3 = y4 = (float)(hh - y) / hh;
|
||||
|
||||
float u1, u2, u3, u4;
|
||||
float v1, v2, v3, v4;
|
||||
|
||||
u1 = u4 = (float)(u) / fw;
|
||||
u2 = u3 = (float)(u+gw) / fw;
|
||||
v1 = v2 = (float)(v+gh) / fh;
|
||||
v3 = v4 = (float)(v) / fh;
|
||||
|
||||
push((struct vertex){ .pos.x=x1, .pos.y=y1, .texture.x=u1, .texture.y=v1 });
|
||||
push((struct vertex){ .pos.x=x2, .pos.y=y2, .texture.x=u2, .texture.y=v2 });
|
||||
push((struct vertex){ .pos.x=x3, .pos.y=y3, .texture.x=u3, .texture.y=v3 });
|
||||
push((struct vertex){ .pos.x=x1, .pos.y=y1, .texture.x=u1, .texture.y=v1 });
|
||||
push((struct vertex){ .pos.x=x3, .pos.y=y3, .texture.x=u3, .texture.y=v3 });
|
||||
push((struct vertex){ .pos.x=x4, .pos.y=y4, .texture.x=u4, .texture.y=v4 });
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void vstack_clear(void)
|
||||
{
|
||||
vstack.prev_hash = vstack.hash;
|
||||
vstack.prev_idx = vstack.idx;
|
||||
vstack.idx = 0;
|
||||
}
|
||||
|
||||
|
||||
// copy the vertex buffer from system to video memory
|
||||
void ren_initvertbuffer()
|
||||
{
|
||||
// generate a buffer id
|
||||
glGenBuffers(1, &ren.gl_vertbuffer);
|
||||
// tell opengl that we want to work on that buffer
|
||||
glBindBuffer(GL_ARRAY_BUFFER, ren.gl_vertbuffer);
|
||||
// copy the vertex data into the gpu memory, GL_STATIC_DRAW tells opengl
|
||||
// that the data will be used for drawing (DRAW) and it will only be modified
|
||||
// every frame (DYNAMIC)
|
||||
glBufferData(GL_ARRAY_BUFFER, vstack.idx*sizeof(struct vertex), vstack.v, GL_DYNAMIC_DRAW);
|
||||
// set the format of each vertex, in this case each vertex is made of 4
|
||||
// coordinates, x y z w, where w is the clip coordinate, stride is
|
||||
// sizeof(vec4) since every postition vertex there is a vector representing
|
||||
// color data
|
||||
// set the position data of the vertex buffer, and bind it as input to the
|
||||
// vertex shader with an index of 0
|
||||
glEnableVertexAttribArray(vertindex);
|
||||
glVertexAttribPointer(vertindex, 2, GL_FLOAT, GL_FALSE, sizeof(struct vertex), 0);
|
||||
// set the color data of the vertex buffer to index 1
|
||||
// vertex attribute is the OpenGL name given to a set of vertices which are
|
||||
// given as input to a vertext shader, in a shader an array of vertices is
|
||||
// always referred to by index and not by pointer or other manners, indices
|
||||
// go from 0 to 15
|
||||
glEnableVertexAttribArray(colindex);
|
||||
glVertexAttribPointer(colindex, 4, GL_FLOAT, GL_FALSE, sizeof(struct vertex), (void*)(2*sizeof(vec2)));
|
||||
// texture uv data
|
||||
glEnableVertexAttribArray(textindex);
|
||||
glVertexAttribPointer(textindex, 2, GL_FLOAT, GL_FALSE, sizeof(struct vertex), (void*)sizeof(vec2));
|
||||
// reset the object bind so not to create errors
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
|
||||
void map_file(const char **str, int *size, const char *fname)
|
||||
{
|
||||
FILE *fp = fopen(fname, "r");
|
||||
*size = lseek(fileno(fp), 0, SEEK_END);
|
||||
*str = mmap(0, *size, PROT_READ, MAP_PRIVATE, fileno(fp), 0);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
|
||||
// print shader compilation errors
|
||||
int debug_shader(GLuint shader)
|
||||
{
|
||||
GLint status;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
|
||||
if (status != GL_FALSE)
|
||||
return 0;
|
||||
|
||||
GLint log_length;
|
||||
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
|
||||
|
||||
GLchar *log_str = malloc((log_length + 1)*sizeof(GLchar));
|
||||
glGetShaderInfoLog(shader, log_length, NULL, log_str);
|
||||
|
||||
const char *shader_type_str = NULL;
|
||||
GLint shader_type;
|
||||
glGetShaderiv(shader, GL_SHADER_TYPE, &shader_type);
|
||||
switch(shader_type) {
|
||||
case GL_VERTEX_SHADER: shader_type_str = "vertex"; break;
|
||||
case GL_GEOMETRY_SHADER: shader_type_str = "geometry"; break;
|
||||
case GL_FRAGMENT_SHADER: shader_type_str = "fragment"; break;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Compile failure in %s shader:\n%s\n", shader_type_str, log_str);
|
||||
free(log_str);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// print program compilation errors
|
||||
int debug_program(GLuint prog)
|
||||
{
|
||||
GLint status;
|
||||
glGetProgramiv (prog, GL_LINK_STATUS, &status);
|
||||
if (status != GL_FALSE)
|
||||
return 0;
|
||||
|
||||
GLint log_length;
|
||||
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &log_length);
|
||||
|
||||
GLchar *log_str = malloc((log_length + 1)*sizeof(GLchar));
|
||||
glGetProgramInfoLog(prog, log_length, NULL, log_str);
|
||||
fprintf(stderr, "Linker failure: %s\n", log_str);
|
||||
free(log_str);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
void ren_initshaders()
|
||||
{
|
||||
GLuint gl_vertshader, gl_fragshader;
|
||||
|
||||
// initialize the vertex shader and get the corresponding id
|
||||
gl_vertshader = glCreateShader(GL_VERTEX_SHADER);
|
||||
if (!gl_vertshader)
|
||||
err(-1, "Could not create the vertex shader");
|
||||
|
||||
// map the shader file into memory
|
||||
const char *vshader_str = NULL;
|
||||
int vshader_size;
|
||||
map_file(&vshader_str, &vshader_size, GLSL_VERT_SHADER);
|
||||
|
||||
// get the shader into opengl
|
||||
glShaderSource(gl_vertshader, 1, &vshader_str, NULL);
|
||||
// compile the shader
|
||||
glCompileShader(gl_vertshader);
|
||||
if (debug_shader(gl_vertshader))
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
// do the same for the fragment shader
|
||||
// FIXME: make this a function
|
||||
gl_fragshader = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
if (!gl_fragshader)
|
||||
err(-1, "Could not create the vertex shader");
|
||||
|
||||
// map the shader file into memory
|
||||
const char *fshader_str = NULL;
|
||||
int fshader_size;
|
||||
map_file(&fshader_str, &fshader_size, GLSL_FRAG_SHADER);
|
||||
|
||||
// get the shader into opengl
|
||||
glShaderSource(gl_fragshader, 1, &fshader_str, NULL);
|
||||
// compile the shader
|
||||
glCompileShader(gl_fragshader);
|
||||
if (debug_shader(gl_fragshader))
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
// create the main program object, it is an amalgamation of all shaders
|
||||
ren.gl_program = glCreateProgram();
|
||||
// attach the shaders to the program (set which shaders are present)
|
||||
glAttachShader(ren.gl_program, gl_vertshader);
|
||||
glAttachShader(ren.gl_program, gl_fragshader);
|
||||
// then link the program (basically the linking stage of the program)
|
||||
glLinkProgram(ren.gl_program);
|
||||
if (debug_program(ren.gl_program))
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
// after linking the shaders can be detached and the source freed from
|
||||
// memory since the program is ready to use
|
||||
glDetachShader(ren.gl_program, gl_vertshader);
|
||||
glDetachShader(ren.gl_program, gl_fragshader);
|
||||
munmap((void *)vshader_str, vshader_size);
|
||||
munmap((void *)fshader_str, fshader_size);
|
||||
|
||||
// now tell opengl to use the program
|
||||
glUseProgram(ren.gl_program);
|
||||
}
|
||||
|
||||
|
||||
void ren_drawvertbuffer()
|
||||
{
|
||||
if (!changed())
|
||||
return;
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, ren.gl_vertbuffer);
|
||||
// upload vertex data
|
||||
if (vstack.idx != vstack.prev_idx)
|
||||
glBufferData(GL_ARRAY_BUFFER, vstack.idx*sizeof(struct vertex), vstack.v, GL_DYNAMIC_DRAW);
|
||||
else
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, vstack.idx*sizeof(struct vertex), vstack.v);
|
||||
// draw vertex data
|
||||
glDrawArrays(GL_TRIANGLES, 0, vstack.idx);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
SDL_GL_SwapWindow(ren.w);
|
||||
}
|
||||
|
||||
|
||||
int w_height(SDL_Window *win)
|
||||
{
|
||||
int h;
|
||||
SDL_GetWindowSize(win, NULL, &h);
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
int w_width(SDL_Window *win)
|
||||
{
|
||||
int w;
|
||||
SDL_GetWindowSize(win, &w, NULL);
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
void import_font(const char *path)
|
||||
{
|
||||
const char *map;
|
||||
int size;
|
||||
const struct _ff *img;
|
||||
|
||||
map_file(&map, &size, path);
|
||||
img = (const struct _ff *)map;
|
||||
fw = ntohl(img->w);
|
||||
fh = ntohl(img->h);
|
||||
|
||||
glGenTextures(1, &ren.font_texture);
|
||||
glBindTexture(GL_TEXTURE_2D, ren.font_texture);
|
||||
// farbfeld image
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_SHORT, img->bytes);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
munmap((void *)map, size);
|
||||
}
|
||||
|
||||
|
||||
void push_text(int x, int y, float scale, const char *s)
|
||||
{
|
||||
for (; *s; s++) {
|
||||
int u, v;
|
||||
int idx = *s - ' ';
|
||||
u = idx % (fw / gw);
|
||||
v = (idx / (fw / gw)) % (fh / gh);
|
||||
vstack_push_quad_t(x, y, gw*scale, gh*scale, u*gw, v*gh);
|
||||
x += gw*scale;
|
||||
if (*s == '\n')
|
||||
y += gh;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main (void)
|
||||
{
|
||||
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
|
||||
SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
|
||||
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
|
||||
|
||||
ren.w = SDL_CreateWindow("test",
|
||||
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
|
||||
500, 500, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE );
|
||||
|
||||
// create the OpenGL context
|
||||
ren.gl = SDL_GL_CreateContext(ren.w);
|
||||
if (ren.gl == NULL)
|
||||
err(-1, "Failed to create OpenGL context: %s", SDL_GetError());
|
||||
|
||||
// select some features
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glDisable(GL_CULL_FACE);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
|
||||
// initialize glew, this library gives us the declarations for most GL
|
||||
// functions, in the future it would be cool to do it manually
|
||||
GLenum glew_err = glewInit();
|
||||
if (glew_err != GLEW_OK)
|
||||
err(-1, "Failed to initialize GLEW: %s", glewGetErrorString(glew_err));
|
||||
|
||||
ren_initshaders();
|
||||
import_font("./charmap.ff");
|
||||
|
||||
vec4 magenta = {.r=1.0, .g=0.0, .b=1.0, .a=1.0};
|
||||
|
||||
ren_initvertbuffer();
|
||||
|
||||
glClearColor(0.3f, 0.3f, 0.3f, 0.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
// event loop and drawing
|
||||
SDL_Event ev = {0};
|
||||
int running = 1;
|
||||
do {
|
||||
SDL_WaitEvent(&ev);
|
||||
switch (ev.type) {
|
||||
case SDL_QUIT: running = 0; break;
|
||||
case SDL_WINDOWEVENT:
|
||||
if(ev.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
|
||||
glViewport(0, 0, w_width(ren.w), w_height(ren.w));
|
||||
glScissor(0, 0, w_width(ren.w), w_height(ren.w));
|
||||
force_changed();
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
vstack_push_quad_c(0, 0, 100, 100, magenta);
|
||||
vstack_push_quad_c(200, 0, 10, 10, magenta);
|
||||
vstack_push_quad_c(10, 150, 100, 100, magenta);
|
||||
push_text(250, 250, 1.0f, "Ciao Victoria <3");
|
||||
update_hash();
|
||||
|
||||
ren_drawvertbuffer();
|
||||
|
||||
vstack_clear();
|
||||
|
||||
} while(running);
|
||||
|
||||
glUseProgram(0);
|
||||
glDisableVertexAttribArray(vertindex);
|
||||
glDisableVertexAttribArray(colindex);
|
||||
glDeleteTextures(1, &ren.font_texture);
|
||||
glDeleteBuffers(1, &ren.gl_vertbuffer);
|
||||
SDL_GL_DeleteContext(ren.gl);
|
||||
SDL_DestroyWindow(ren.w);
|
||||
SDL_Quit();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
#version 330
|
||||
|
||||
// use the input ("in") vertices from index zero
|
||||
layout(location = 0) in vec2 position;
|
||||
layout(location = 1) in vec4 color;
|
||||
layout(location = 2) in vec2 uv;
|
||||
|
||||
flat out vec4 out_color;
|
||||
out vec2 texture_coord;
|
||||
|
||||
void main()
|
||||
{
|
||||
// simply copy teh output position
|
||||
gl_Position = vec4(position.x, position.y, 0.0f, 1.0f);
|
||||
out_color = color;
|
||||
texture_coord = uv;
|
||||
}
|
||||
2
opengl/.gitignore
vendored
2
opengl/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
a.out
|
||||
gl
|
||||
@ -1,5 +0,0 @@
|
||||
CFLAGS = -Wall -Wextra -Wpedantic -std=c11 -g
|
||||
LDFLAGS = -lSDL2 -lGLEW -lGL
|
||||
|
||||
gl: main.c
|
||||
gcc ${CFLAGS} ${LDFLAGS} main.c -o gl
|
||||
@ -1,2 +0,0 @@
|
||||
since I've never worked with opengl I made this folder as a little test environment
|
||||
for an SDL-OpenGL renderer
|
||||
@ -1,10 +0,0 @@
|
||||
#version 330
|
||||
|
||||
smooth in vec4 out_color;
|
||||
out vec4 color;
|
||||
|
||||
void main()
|
||||
{
|
||||
// set the color for each vertex to white
|
||||
color = out_color;
|
||||
}
|
||||
278
opengl/main.c
278
opengl/main.c
@ -1,278 +0,0 @@
|
||||
#define _POSIX_C_SOURCE 200809l
|
||||
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <err.h>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <GL/glew.h>
|
||||
#include <SDL2/SDL_opengl.h>
|
||||
|
||||
|
||||
#define GLSL_VERT_SHADER "vertex.glsl"
|
||||
#define GLSL_FRAG_SHADER "fragment.glsl"
|
||||
#define PACKED __attribute__((packed))
|
||||
|
||||
|
||||
const int vertindex = 0;
|
||||
const int colindex = 1;
|
||||
|
||||
struct {
|
||||
SDL_Window *w;
|
||||
SDL_GLContext *gl;
|
||||
GLuint gl_vertbuffer;
|
||||
GLuint gl_program;
|
||||
} ren = {0};
|
||||
|
||||
typedef struct PACKED {
|
||||
union { GLfloat x, r; };
|
||||
union { GLfloat y, g; };
|
||||
union { GLfloat z, b; };
|
||||
union { GLfloat w, a; };
|
||||
} vec4;
|
||||
|
||||
// a vertex has a position and a color
|
||||
struct PACKED vertex { vec4 pos, col; };
|
||||
|
||||
#define VNUM(s) (sizeof(s)/sizeof(s[0]))
|
||||
struct vertex vertbuffer[] = {
|
||||
{ .pos={.x= 0.75f, .y= 0.75f, .z=0.0f,.w=1.0f}, .col={ .r=1.0f, .g=0.0f, .b=0.0f, .a=1.0f} },
|
||||
{ .pos={.x=-0.75f, .y=-0.75f, .z=0.0f,.w=1.0f}, .col={ .r=1.0f, .g=0.0f, .b=0.0f, .a=1.0f} },
|
||||
{ .pos={.x= 0.75f, .y=-0.75f, .z=0.0f,.w=1.0f}, .col={ .r=1.0f, .g=0.0f, .b=0.0f, .a=1.0f} },
|
||||
|
||||
{ .pos={.x= 0.75f, .y= 0.75f, .z=0.0f,.w=1.0f}, .col={ .r=1.0f, .g=0.0f, .b=0.0f, .a=1.0f} },
|
||||
{ .pos={.x=-0.75f, .y=-0.75f, .z=0.0f,.w=1.0f}, .col={ .r=1.0f, .g=0.0f, .b=0.0f, .a=1.0f} },
|
||||
{ .pos={.x=-0.75f, .y= 0.75f, .z=0.0f,.w=1.0f}, .col={ .r=1.0f, .g=0.0f, .b=0.0f, .a=1.0f} },
|
||||
};
|
||||
|
||||
|
||||
// copy the vertex buffer from system to video memory
|
||||
void ren_initvertbuffer()
|
||||
{
|
||||
// generate a buffer id
|
||||
glGenBuffers(1, &ren.gl_vertbuffer);
|
||||
// tell opengl that we want to work on that buffer
|
||||
glBindBuffer(GL_ARRAY_BUFFER, ren.gl_vertbuffer);
|
||||
// copy the vertex data into the gpu memory, GL_STATIC_DRAW tells opengl
|
||||
// that the data will be used for drawing (DRAW) and it will only be modified
|
||||
// once (STATIC)
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(vertbuffer), &vertbuffer, GL_STATIC_DRAW);
|
||||
// set the format of each vertex, in this case each vertex is made of 4
|
||||
// coordinates, x y z w, where w is the clip coordinate, stride is
|
||||
// sizeof(vec4) since every postition vertex there is a vector representing
|
||||
// color data
|
||||
// set the position data of the vertex buffer, and bind it as input to the
|
||||
// vertex shader with an index of 0
|
||||
glEnableVertexAttribArray(vertindex);
|
||||
glVertexAttribPointer(vertindex, 4, GL_FLOAT, GL_FALSE, sizeof(struct vertex), 0);
|
||||
// set the color data of the vertex buffer to index 1
|
||||
// vertex attribute is the OpenGL name given to a set of vertices which are
|
||||
// given as input to a vertext shader, in a shader an array of vertices is
|
||||
// always referred to by index and not by pointer or other manners, indices
|
||||
// go from 0 to 15
|
||||
glEnableVertexAttribArray(colindex);
|
||||
glVertexAttribPointer(colindex, 4, GL_FLOAT, GL_FALSE, sizeof(struct vertex), (void*)sizeof(vec4));
|
||||
// reset the object bind so not to create errors
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
|
||||
void map_file(const char **str, int *size, const char *fname)
|
||||
{
|
||||
FILE *fp = fopen(fname, "r");
|
||||
*size = lseek(fileno(fp), 0, SEEK_END);
|
||||
*str = mmap(0, *size, PROT_READ, MAP_PRIVATE, fileno(fp), 0);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
|
||||
// print shader compilation errors
|
||||
int debug_shader(GLuint shader)
|
||||
{
|
||||
GLint status;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
|
||||
if (status != GL_FALSE)
|
||||
return 0;
|
||||
|
||||
GLint log_length;
|
||||
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
|
||||
|
||||
GLchar *log_str = malloc((log_length + 1)*sizeof(GLchar));
|
||||
glGetShaderInfoLog(shader, log_length, NULL, log_str);
|
||||
|
||||
const char *shader_type_str = NULL;
|
||||
GLint shader_type;
|
||||
glGetShaderiv(shader, GL_SHADER_TYPE, &shader_type);
|
||||
switch(shader_type) {
|
||||
case GL_VERTEX_SHADER: shader_type_str = "vertex"; break;
|
||||
case GL_GEOMETRY_SHADER: shader_type_str = "geometry"; break;
|
||||
case GL_FRAGMENT_SHADER: shader_type_str = "fragment"; break;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Compile failure in %s shader:\n%s\n", shader_type_str, log_str);
|
||||
free(log_str);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// print program compilation errors
|
||||
int debug_program(GLuint prog)
|
||||
{
|
||||
GLint status;
|
||||
glGetProgramiv (prog, GL_LINK_STATUS, &status);
|
||||
if (status != GL_FALSE)
|
||||
return 0;
|
||||
|
||||
GLint log_length;
|
||||
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &log_length);
|
||||
|
||||
GLchar *log_str = malloc((log_length + 1)*sizeof(GLchar));
|
||||
glGetProgramInfoLog(prog, log_length, NULL, log_str);
|
||||
fprintf(stderr, "Linker failure: %s\n", log_str);
|
||||
free(log_str);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
void ren_initshaders()
|
||||
{
|
||||
GLuint gl_vertshader, gl_fragshader;
|
||||
|
||||
// initialize the vertex shader and get the corresponding id
|
||||
gl_vertshader = glCreateShader(GL_VERTEX_SHADER);
|
||||
if (!gl_vertshader)
|
||||
err(-1, "Could not create the vertex shader");
|
||||
|
||||
// map the shader file into memory
|
||||
const char *vshader_str = NULL;
|
||||
int vshader_size;
|
||||
map_file(&vshader_str, &vshader_size, GLSL_VERT_SHADER);
|
||||
|
||||
// get the shader into opengl
|
||||
glShaderSource(gl_vertshader, 1, &vshader_str, NULL);
|
||||
// compile the shader
|
||||
glCompileShader(gl_vertshader);
|
||||
if (debug_shader(gl_vertshader))
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
// do the same for the fragment shader
|
||||
// FIXME: make this a function
|
||||
gl_fragshader = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
if (!gl_fragshader)
|
||||
err(-1, "Could not create the vertex shader");
|
||||
|
||||
// map the shader file into memory
|
||||
const char *fshader_str = NULL;
|
||||
int fshader_size;
|
||||
map_file(&fshader_str, &fshader_size, GLSL_FRAG_SHADER);
|
||||
|
||||
// get the shader into opengl
|
||||
glShaderSource(gl_fragshader, 1, &fshader_str, NULL);
|
||||
// compile the shader
|
||||
glCompileShader(gl_fragshader);
|
||||
if (debug_shader(gl_fragshader))
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
// create the main program object, it is an amalgamation of all shaders
|
||||
ren.gl_program = glCreateProgram();
|
||||
// attach the shaders to the program (set which shaders are present)
|
||||
glAttachShader(ren.gl_program, gl_vertshader);
|
||||
glAttachShader(ren.gl_program, gl_fragshader);
|
||||
// then link the program (basically the linking stage of the program)
|
||||
glLinkProgram(ren.gl_program);
|
||||
if (debug_program(ren.gl_program))
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
// after linking the shaders can be detached and the source freed from
|
||||
// memory since the program is ready to use
|
||||
glDetachShader(ren.gl_program, gl_vertshader);
|
||||
glDetachShader(ren.gl_program, gl_fragshader);
|
||||
munmap((void *)vshader_str, vshader_size);
|
||||
munmap((void *)fshader_str, fshader_size);
|
||||
|
||||
// now tell opengl to use the program
|
||||
glUseProgram(ren.gl_program);
|
||||
}
|
||||
|
||||
|
||||
void ren_drawvertbuffer()
|
||||
{
|
||||
glBindBuffer(GL_ARRAY_BUFFER, ren.gl_vertbuffer);
|
||||
glDrawArrays(GL_TRIANGLES, 0, VNUM(vertbuffer));
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
|
||||
int w_height(SDL_Window *win)
|
||||
{
|
||||
int h;
|
||||
SDL_GetWindowSize(win, NULL, &h);
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
int w_width(SDL_Window *win)
|
||||
{
|
||||
int w;
|
||||
SDL_GetWindowSize(win, &w, NULL);
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
int main (void)
|
||||
{
|
||||
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
|
||||
SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
|
||||
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
|
||||
|
||||
ren.w = SDL_CreateWindow("test",
|
||||
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
|
||||
500, 500, SDL_WINDOW_OPENGL );
|
||||
|
||||
// create the OpenGL context
|
||||
ren.gl = SDL_GL_CreateContext(ren.w);
|
||||
if (ren.gl == NULL)
|
||||
err(-1, "Failed to create OpenGL context: %s", SDL_GetError());
|
||||
|
||||
// initialize glew, this library gives us the declarations for most GL
|
||||
// functions, in the future it would be cool to do it manually
|
||||
GLenum glew_err = glewInit();
|
||||
if (glew_err != GLEW_OK)
|
||||
err(-1, "Failed to initialize GLEW: %s", glewGetErrorString(glew_err));
|
||||
|
||||
ren_initvertbuffer();
|
||||
ren_initshaders();
|
||||
|
||||
// event loop and drawing
|
||||
SDL_Event ev = {0};
|
||||
int running = 1;
|
||||
do {
|
||||
SDL_WaitEvent(&ev);
|
||||
switch (ev.type) {
|
||||
case SDL_QUIT: running = 0; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
glViewport(0, 0, w_width(ren.w), w_height(ren.w));
|
||||
glClearColor(0.f, 0.f, 0.f, 0.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
ren_drawvertbuffer();
|
||||
|
||||
SDL_GL_SwapWindow(ren.w);
|
||||
|
||||
} while(running);
|
||||
|
||||
glDisableVertexAttribArray(vertindex);
|
||||
glDisableVertexAttribArray(colindex);
|
||||
SDL_GL_DeleteContext(ren.gl);
|
||||
SDL_DestroyWindow(ren.w);
|
||||
SDL_Quit();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
#version 330
|
||||
|
||||
// use the input ("in") vertices from index zero
|
||||
layout(location = 0) in vec4 position;
|
||||
layout(location = 1) in vec4 color;
|
||||
|
||||
smooth out vec4 out_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
// simply copy teh output position
|
||||
gl_Position = position;
|
||||
out_color = color;
|
||||
}
|
||||
@ -1,126 +0,0 @@
|
||||
#ifndef _STACK_GENERIC_H
|
||||
#define _STACK_GENERIC_H
|
||||
|
||||
|
||||
#define STACK_STEP 8
|
||||
#define STACK_SALT 0xbabb0cac
|
||||
|
||||
// FIXME: find a way to not re-hash the whole stack when removing one item
|
||||
|
||||
// incremental hash for every grow
|
||||
#define STACK_HASH(p, s, h) \
|
||||
{ \
|
||||
unsigned char *v = (unsigned char *)(p); \
|
||||
for (int x = (s); x; x--) { \
|
||||
(h) += v[x-1]; \
|
||||
(h) += (h) << 10; \
|
||||
(h) ^= (h) >> 6; \
|
||||
} \
|
||||
(h) += (h) << 3; \
|
||||
(h) ^= (h) >> 11; \
|
||||
(h) += (h) << 15; \
|
||||
}
|
||||
|
||||
|
||||
// declare just the prototypes
|
||||
#define STACK_PROTO(stackname, type) \
|
||||
struct stackname { \
|
||||
type *items; \
|
||||
int size, idx, old_idx; \
|
||||
unsigned int hash, old_hash; \
|
||||
}; \
|
||||
struct stackname stackname##_init(void); \
|
||||
int stackname##_grow(struct stackname *stack, int step); \
|
||||
int stackname##_push(struct stackname *stack, type *e); \
|
||||
type stackname##_pop(struct stackname *stack); \
|
||||
int stackname##_clear(struct stackname *stack); \
|
||||
int stackname##_changed(struct stackname *stack); \
|
||||
int stackname##_size_changed(struct stackname *stack); \
|
||||
int stackname##_free(struct stackname *stack);
|
||||
|
||||
|
||||
#define STACK_DEFINE(stackname, type) \
|
||||
struct stackname; \
|
||||
\
|
||||
\
|
||||
struct stackname stackname##_init(void) \
|
||||
{ \
|
||||
return (struct stackname){0, .hash = STACK_SALT}; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_grow(struct stackname *stack, int step) \
|
||||
{ \
|
||||
if (!stack) \
|
||||
return -1; \
|
||||
stack->items = realloc(stack->items, (stack->size+step)*sizeof(type)); \
|
||||
if(!stack->items) \
|
||||
return -1; \
|
||||
memset(&(stack->items[stack->size]), 0, step*sizeof(*(stack->items))); \
|
||||
stack->size += step; \
|
||||
return 0; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_push(struct stackname *stack, type *e) \
|
||||
{ \
|
||||
if (!stack || !e) \
|
||||
return -1; \
|
||||
if (stack->idx >= stack->size) \
|
||||
if (stackname##_grow(stack, STACK_STEP)) \
|
||||
return -1; \
|
||||
stack->items[stack->idx++] = *e; \
|
||||
STACK_HASH(e, sizeof(type), stack->hash); \
|
||||
return 0; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
type stackname##_pop(struct stackname *stack) \
|
||||
{ \
|
||||
if (!stack || stack->idx == 0 || stack->size == 0) \
|
||||
return (type){0}; \
|
||||
stack->hash = STACK_SALT; \
|
||||
STACK_HASH(stack->items, sizeof(type)*(stack->idx-1), stack->hash); \
|
||||
return stack->items[stack->idx--]; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_clear(struct stackname *stack) \
|
||||
{ \
|
||||
if (!stack) \
|
||||
return -1; \
|
||||
stack->old_idx = stack->idx; \
|
||||
stack->old_hash = stack->hash; \
|
||||
stack->hash = STACK_SALT; \
|
||||
stack->idx = 0; \
|
||||
return 0; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_changed(struct stackname *stack) \
|
||||
{ \
|
||||
if (!stack) \
|
||||
return -1; \
|
||||
return stack->hash != stack->old_hash; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_size_changed(struct stackname *stack) \
|
||||
{ \
|
||||
if (!stack) \
|
||||
return -1; \
|
||||
return stack->size != stack->old_idx; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_free(struct stackname *stack) \
|
||||
{ \
|
||||
if (stack) { \
|
||||
stackname##_clear(stack); \
|
||||
if (stack->items) \
|
||||
free(stack->items); \
|
||||
} \
|
||||
return 0; \
|
||||
} \
|
||||
|
||||
#endif
|
||||
0
scripts/.gitkeep
Normal file
0
scripts/.gitkeep
Normal file
131
src/atlas.c3
Normal file
131
src/atlas.c3
Normal file
@ -0,0 +1,131 @@
|
||||
module ugui;
|
||||
|
||||
import std::io;
|
||||
|
||||
faultdef CANNOT_PLACE, INVALID_TYPE;
|
||||
|
||||
enum AtlasType {
|
||||
ATLAS_GRAYSCALE,
|
||||
ATLAS_R8G8B8A8,
|
||||
}
|
||||
|
||||
// black and white atlas
|
||||
struct Atlas {
|
||||
AtlasType type;
|
||||
Id id;
|
||||
|
||||
ushort width, height;
|
||||
char[] buffer;
|
||||
|
||||
Point row;
|
||||
ushort row_h;
|
||||
}
|
||||
|
||||
// bytes per pixel
|
||||
macro usz AtlasType.bpp(type)
|
||||
{
|
||||
switch (type) {
|
||||
case ATLAS_GRAYSCALE: return 1;
|
||||
case ATLAS_R8G8B8A8: return 4;
|
||||
}
|
||||
}
|
||||
|
||||
macro typeid AtlasType.underlying(type)
|
||||
{
|
||||
switch (type) {
|
||||
case ATLAS_GRAYSCALE: return char;
|
||||
case ATLAS_R8G8B8A8: return uint;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// FIXME: in and out types are not always known at compile time
|
||||
macro @pixel_convert(p, AtlasType $in, AtlasType $out)
|
||||
{
|
||||
$if $in == $out:
|
||||
return p;
|
||||
$else
|
||||
$switch
|
||||
$case $in == ATLAS_R8G8B8A8 && $out == ATLAS_GRAYSCALE:
|
||||
var r = ((p >> 0) & 0xff);
|
||||
var g = ((p >> 8) & 0xff);
|
||||
var b = ((p >> 16) & 0xff);
|
||||
var a = ((p >> 24) & 0xff);
|
||||
if (a == 0) return (char)0;
|
||||
return (ATLAS_GRAYSCALE.underlying())(((float)r+g+b) / 3.0f);
|
||||
$case $in == ATLAS_GRAYSCALE && $out == ATLAS_R8G8B8A8:
|
||||
var x = (char)(p/3.0);
|
||||
return (ATLAS_R8G8B8A8.underlying())(x|(x<<8)|(x<<16)|(255<<24));
|
||||
$default: $error "Unimplemented pixel format conversion";
|
||||
$endswitch
|
||||
$endif
|
||||
}
|
||||
*/
|
||||
|
||||
fn void? Atlas.new(&atlas, Id id, AtlasType type, ushort width, ushort height)
|
||||
{
|
||||
atlas.id = id;
|
||||
atlas.type = type;
|
||||
atlas.width = width;
|
||||
atlas.height = height;
|
||||
|
||||
atlas.buffer = mem::new_array(char, (usz)atlas.width*atlas.height*type.bpp());
|
||||
}
|
||||
|
||||
fn void Atlas.free(&atlas)
|
||||
{
|
||||
free(atlas.buffer);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* pixels -> +--------------+-----+
|
||||
* | | | h
|
||||
* | | | e
|
||||
* | | | i
|
||||
* | | | g
|
||||
* | | | h
|
||||
* | | | t
|
||||
* +--------------+-----+
|
||||
* |<--- width -->|
|
||||
* |<----- stride ----->|
|
||||
* bytes per pixels are inferred and have to be the same
|
||||
* as the atlas type
|
||||
*/
|
||||
// place a rect inside the atlas
|
||||
// uses a row first algorithm
|
||||
// TODO: use a skyline algorithm https://jvernay.fr/en/blog/skyline-2d-packer/implementation/
|
||||
fn Point? Atlas.place(&atlas, char[] pixels, ushort w, ushort h, ushort stride)
|
||||
{
|
||||
Point p;
|
||||
|
||||
if (atlas.row.x + w <= atlas.width && atlas.row.y + h <= atlas.height) {
|
||||
p = atlas.row;
|
||||
} else {
|
||||
atlas.row.x = 0;
|
||||
atlas.row.y = atlas.row.y + atlas.row_h;
|
||||
atlas.row_h = 0;
|
||||
if (atlas.row.x + w <= atlas.width && atlas.row.y + h <= atlas.height) {
|
||||
p = atlas.row;
|
||||
} else {
|
||||
return CANNOT_PLACE?;
|
||||
}
|
||||
}
|
||||
|
||||
usz bpp = atlas.type.bpp();
|
||||
for (usz y = 0; y < h; y++) {
|
||||
for (usz x = 0; x < w; x++) {
|
||||
char[] buf = atlas.buffer[(usz)(p.y+y)*atlas.width*bpp + (p.x+x)*bpp ..];
|
||||
char[] pix = pixels[(usz)y*stride*bpp + x*bpp ..];
|
||||
|
||||
buf[0..bpp-1] = pix[0..bpp-1];
|
||||
}
|
||||
}
|
||||
|
||||
atlas.row.x += w;
|
||||
if (h > atlas.row_h) {
|
||||
atlas.row_h = h;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
149
src/cache.c3
Normal file
149
src/cache.c3
Normal file
@ -0,0 +1,149 @@
|
||||
module cache{Key, Value, SIZE};
|
||||
|
||||
/* LRU Cache
|
||||
* The cache uses a pool (array) to store all the elements, each element has
|
||||
* a key (id) and a value. A HashMap correlates the ids to an index in the pool.
|
||||
* To keep track of which items were recently used two bit arrays are kept, one
|
||||
* stores the "used" flag for each index and anothe the "present" flag.
|
||||
* Every NCYCLES operations the present and used arrays are updated to free up
|
||||
* the elements that were not recently used.
|
||||
*/
|
||||
|
||||
import std::core::mem;
|
||||
import std::core::mem::allocator;
|
||||
import std::collections::bitset;
|
||||
import std::collections::map;
|
||||
|
||||
alias BitArr = bitset::BitSet{SIZE};
|
||||
alias IdTable = map::HashMap{Key, usz};
|
||||
alias IdTableEntry = map::Entry{Key, usz};
|
||||
|
||||
const usz CACHE_NCYCLES = (usz)(SIZE * 2.0/3.0);
|
||||
|
||||
struct Cache {
|
||||
Allocator allocator;
|
||||
BitArr present, used;
|
||||
IdTable table;
|
||||
Value[] pool;
|
||||
usz cycle_count;
|
||||
}
|
||||
|
||||
// Every CACHE_CYCLES operations mark as not-present the unused elements
|
||||
macro Cache.cycle(&cache) @private {
|
||||
cache.cycle_count++;
|
||||
if (cache.cycle_count > CACHE_NCYCLES) {
|
||||
for (usz i = 0; i < cache.present.data.len; i++) {
|
||||
cache.present.data[i] &= cache.used.data[i];
|
||||
cache.used.data[i] = 0;
|
||||
}
|
||||
cache.cycle_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn void? Cache.init(&cache, Allocator allocator)
|
||||
{
|
||||
cache.allocator = allocator;
|
||||
cache.table.init(allocator, capacity: SIZE);
|
||||
// FIXME: this shit is SLOW
|
||||
foreach (idx, bit : cache.used) { cache.used[idx] = false; }
|
||||
foreach (idx, bit : cache.present) { cache.present[idx] = false; }
|
||||
cache.pool = allocator::new_array(allocator, Value, SIZE);
|
||||
}
|
||||
|
||||
fn void Cache.free(&cache)
|
||||
{
|
||||
(void)cache.table.free();
|
||||
(void)allocator::free(cache.allocator, cache.pool);
|
||||
}
|
||||
|
||||
fn Value*? Cache.search(&cache, Key id)
|
||||
{
|
||||
// get_entry() faults on miss
|
||||
IdTableEntry* entry = cache.table.get_entry(id)!;
|
||||
|
||||
/* MISS, wrong key */
|
||||
if (entry.key != id) {
|
||||
cache.table.remove(id)!;
|
||||
return NOT_FOUND?;
|
||||
}
|
||||
|
||||
/* MISS, the data is not valid (not present) */
|
||||
if (!cache.present[entry.value]) {
|
||||
// if the data is not present but it is still in the table, remove it
|
||||
cache.table.remove(id)!;
|
||||
return NOT_FOUND?;
|
||||
}
|
||||
|
||||
/* HIT, set as recently used */
|
||||
//io::printfn("HIT: %d [%d]", entry.value, entry.key);
|
||||
cache.used[entry.value] = true;
|
||||
return &(cache.pool[entry.value]);
|
||||
}
|
||||
|
||||
fn void Cache.remove(&cache, Key id)
|
||||
{
|
||||
IdTableEntry*? entry = cache.table.get_entry(id);
|
||||
if (catch entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
// found, remove it
|
||||
cache.present[entry.value] = false;
|
||||
(void)cache.table.remove(id);
|
||||
}
|
||||
|
||||
/* Look for a free spot in the present bitmap and return its index */
|
||||
/* If there is no free space left then just return the first position */
|
||||
fn usz Cache.get_free_spot(&cache) @private
|
||||
{
|
||||
// TODO: in the upgrade to c3 1.7.5 use @bitsof()
|
||||
const BITS = $typeof(cache.present.data[0]).sizeof*8;
|
||||
foreach (idx, d: cache.present.data) {
|
||||
if (d != $typeof(d).max) {
|
||||
usz spot = idx*BITS + BITS-d.clz();
|
||||
if (cache.used[spot]) unreachable("free spot is not actually free: %d", spot);
|
||||
return spot;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn Value*? Cache.insert_at(&cache, Value* g, Key id, usz index) @private
|
||||
{
|
||||
// TODO: verify index, g and id
|
||||
Value* spot;
|
||||
|
||||
/* Set used and present */
|
||||
cache.present.set(index);
|
||||
cache.used.set(index);
|
||||
cache.cycle();
|
||||
|
||||
spot = &(cache.pool[index]);
|
||||
*spot = *g;
|
||||
cache.table.set(id, index);
|
||||
return spot;
|
||||
}
|
||||
|
||||
// Insert an element in the cache, returns the index
|
||||
fn Value*? Cache.insert_new(&cache, Value* g, Key id)
|
||||
{
|
||||
usz index = cache.get_free_spot();
|
||||
return cache.insert_at(g, id, index);
|
||||
}
|
||||
|
||||
fn Value*? Cache.get_or_insert(&cache, Value* g, Key id, bool *is_new = null)
|
||||
{
|
||||
Value*? c = cache.search(id);
|
||||
if (catch e = c) {
|
||||
if (e != NOT_FOUND) {
|
||||
return e?;
|
||||
} else {
|
||||
// if the element is new (inserted) set the is_new flag
|
||||
if (is_new) *is_new = true;
|
||||
return cache.insert_new(g, id);
|
||||
}
|
||||
} else {
|
||||
if (is_new) *is_new = false;
|
||||
return c;
|
||||
}
|
||||
}
|
||||
217
src/cmd.c3
Normal file
217
src/cmd.c3
Normal file
@ -0,0 +1,217 @@
|
||||
module ugui;
|
||||
|
||||
import std::io;
|
||||
import std::collections::list;
|
||||
|
||||
// command type
|
||||
enum CmdType {
|
||||
CMD_RECT,
|
||||
CMD_UPDATE_ATLAS,
|
||||
CMD_SPRITE,
|
||||
CMD_SCISSOR,
|
||||
CMD_REQ_SKIP_FRAME,
|
||||
}
|
||||
|
||||
// command to draw a rect
|
||||
struct CmdRect {
|
||||
Rect rect;
|
||||
ushort radius;
|
||||
Color color;
|
||||
}
|
||||
|
||||
struct CmdUpdateAtlas {
|
||||
Id id;
|
||||
char* raw_buffer;
|
||||
short width, height, bpp;
|
||||
}
|
||||
|
||||
struct CmdSprite {
|
||||
Id texture_id;
|
||||
SpriteType type;
|
||||
Rect rect;
|
||||
Rect texture_rect;
|
||||
Color hue;
|
||||
}
|
||||
|
||||
// if rect is zero Rect{0} then reset the scissor
|
||||
struct CmdScissor {
|
||||
Rect rect;
|
||||
}
|
||||
|
||||
// command structure
|
||||
struct Cmd (Printable) {
|
||||
CmdType type;
|
||||
int z_index;
|
||||
union {
|
||||
CmdRect rect;
|
||||
CmdUpdateAtlas update_atlas;
|
||||
CmdSprite sprite;
|
||||
CmdScissor scissor;
|
||||
}
|
||||
}
|
||||
|
||||
// command queue
|
||||
alias CmdQueue = list::List{Cmd};
|
||||
|
||||
|
||||
fn int Cmd.compare_to(Cmd a, Cmd b)
|
||||
{
|
||||
if (a.z_index == b.z_index) return 0;
|
||||
return a.z_index > b.z_index ? 1 : -1;
|
||||
}
|
||||
|
||||
|
||||
// FIXME: This sorting method does not fully respect layer ordering for popups.
|
||||
// Popups that start on the same layer but are enqueued at different times
|
||||
// may still be rendered on top of each other, even if the first popup
|
||||
// has elements in higher layers than the second. The current approach
|
||||
// does not truly sort by layer; it only moves elements from higher layers
|
||||
// to the end of the queue as they are encountered.
|
||||
fn void? CmdQueue.sort(&queue)
|
||||
{
|
||||
CmdQueue stack;
|
||||
stack.init(allocator::tmem);
|
||||
|
||||
for (isz i = queue.len()-1; i > 0; i--) {
|
||||
Cmd cur = (*queue)[i];
|
||||
Cmd next = (*queue)[i - 1];
|
||||
// cur < next
|
||||
if (cur.compare_to(next) < 0) {
|
||||
stack.push(next);
|
||||
queue.remove_at(i-1);
|
||||
}
|
||||
}
|
||||
|
||||
usz l = stack.len();
|
||||
for (usz i; i < l; i++) {
|
||||
queue.push(stack.pop())!;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// implement the Printable interface
|
||||
fn usz? Cmd.to_format(Cmd* cmd, Formatter *f) @dynamic
|
||||
{
|
||||
usz ret;
|
||||
|
||||
ret += f.printf("Cmd{ type: %s, z_index: %d, ", cmd.type, cmd.z_index)!;
|
||||
switch (cmd.type) {
|
||||
case CMD_RECT:
|
||||
ret += f.print("CmdRect")!;
|
||||
ret += io::struct_to_format(cmd.rect, f, false)!;
|
||||
case CMD_SCISSOR:
|
||||
ret += f.print("CmdScissor")!;
|
||||
ret += io::struct_to_format(cmd.scissor, f, false)!;
|
||||
case CMD_SPRITE:
|
||||
ret += f.print("CmdSprite")!;
|
||||
ret += io::struct_to_format(cmd.sprite, f, false)!;
|
||||
case CMD_UPDATE_ATLAS:
|
||||
ret += f.print("CmdUpdateAtlas")!;
|
||||
ret += io::struct_to_format(cmd.update_atlas, f, false)!;
|
||||
case CMD_REQ_SKIP_FRAME:
|
||||
ret += f.print("Skip Frame Request")!;
|
||||
}
|
||||
|
||||
ret += f.print("}")!;
|
||||
return ret;
|
||||
}
|
||||
|
||||
macro bool cull_rect(Rect rect, Rect clip = {0,0,short.max,short.max})
|
||||
{
|
||||
bool no_area = rect.w <= 0 || rect.h <= 0;
|
||||
return no_area || !rect.collides(clip);
|
||||
}
|
||||
|
||||
// FIXME: this whole thing could be done at compile time, maybe
|
||||
macro Ctx.push_cmd(&ctx, Cmd cmd, int z_index)
|
||||
{
|
||||
cmd.z_index = z_index;
|
||||
Rect rect;
|
||||
switch (cmd.type) {
|
||||
case CMD_RECT: rect = cmd.rect.rect;
|
||||
case CMD_SPRITE: rect = cmd.sprite.rect;
|
||||
default: return ctx.cmd_queue.push(cmd);
|
||||
}
|
||||
if (cull_rect(rect, ctx.div_scissor)) {
|
||||
// println("NOPE: ", cmd.rect.rect, cmd.z_index);
|
||||
// unreachable();
|
||||
return;
|
||||
}
|
||||
return ctx.cmd_queue.push(cmd);
|
||||
}
|
||||
|
||||
fn void? Ctx.push_scissor(&ctx, Rect rect, int z_index)
|
||||
{
|
||||
Cmd sc = {
|
||||
.type = CMD_SCISSOR,
|
||||
.scissor.rect = rect.intersection(ctx.div_scissor),
|
||||
};
|
||||
ctx.push_cmd(sc, z_index);
|
||||
}
|
||||
|
||||
fn void? Ctx.reset_scissor(&ctx, int z_index) => ctx.push_cmd({.type=CMD_SCISSOR,.scissor.rect=ctx.div_scissor}, z_index);
|
||||
|
||||
fn void? Ctx.push_rect(&ctx, Rect rect, int z_index, Style* style)
|
||||
{
|
||||
Rect border = style.border;
|
||||
ushort radius = style.radius;
|
||||
Color bg = style.bg;
|
||||
Color border_color = style.secondary;
|
||||
|
||||
// FIXME: this implies that the border has to be uniform
|
||||
if (!border.is_null()) {
|
||||
Cmd cmd = {
|
||||
.type = CMD_RECT,
|
||||
.rect.rect = rect,
|
||||
.rect.color = border_color,
|
||||
.rect.radius = radius + border.x,
|
||||
};
|
||||
ctx.push_cmd(cmd, z_index);
|
||||
}
|
||||
|
||||
Cmd cmd = {
|
||||
.type = CMD_RECT,
|
||||
.rect.rect = {
|
||||
.x = rect.x + border.x,
|
||||
.y = rect.y + border.y,
|
||||
.w = rect.w - (border.x+border.w),
|
||||
.h = rect.h - (border.y+border.h),
|
||||
},
|
||||
.rect.color = bg,
|
||||
.rect.radius = radius,
|
||||
};
|
||||
ctx.push_cmd(cmd, z_index);
|
||||
}
|
||||
|
||||
// TODO: accept a Sprite* instead of all this shit
|
||||
fn void? Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, int z_index, Color hue = 0xffffffffu.to_rgba(), SpriteType type = SPRITE_NORMAL)
|
||||
{
|
||||
Cmd cmd = {
|
||||
.type = CMD_SPRITE,
|
||||
.sprite.type = type,
|
||||
.sprite.rect = bounds,
|
||||
.sprite.texture_rect = texture,
|
||||
.sprite.texture_id = texture_id,
|
||||
.sprite.hue = hue,
|
||||
};
|
||||
ctx.push_cmd(cmd, z_index);
|
||||
}
|
||||
|
||||
fn void? Ctx.push_update_atlas(&ctx, Atlas* atlas)
|
||||
{
|
||||
Cmd up = {
|
||||
.type = CMD_UPDATE_ATLAS,
|
||||
.update_atlas = {
|
||||
.id = atlas.id,
|
||||
.raw_buffer = atlas.buffer,
|
||||
.width = atlas.width,
|
||||
.height = atlas.height,
|
||||
.bpp = (ushort)atlas.type.bpp(),
|
||||
},
|
||||
};
|
||||
// update the atlases before everything else
|
||||
ctx.push_cmd(up, -1);
|
||||
}
|
||||
|
||||
macro Ctx.dbg_rect(&ctx, Rect r, uint c = 0xff000042u) => ctx.push_rect(r, int.max, &&(Style){.bg=c.to_rgba()})!!;
|
||||
|
||||
396
src/core.c3
Normal file
396
src/core.c3
Normal file
@ -0,0 +1,396 @@
|
||||
module ugui;
|
||||
|
||||
import mtree;
|
||||
import cache;
|
||||
|
||||
|
||||
import std::io;
|
||||
import std::core::string;
|
||||
import std::core::mem::allocator;
|
||||
import std::collections::pair;
|
||||
import std::sort;
|
||||
|
||||
|
||||
macro println(...)
|
||||
{
|
||||
$for var $i = 0; $i < $vacount; $i++:
|
||||
io::print($vaexpr[$i]);
|
||||
$endfor
|
||||
io::printn();
|
||||
}
|
||||
|
||||
|
||||
// element ids are just long ints
|
||||
alias Id = uint;
|
||||
|
||||
enum ElemType {
|
||||
ETYPE_NONE,
|
||||
ETYPE_DIV,
|
||||
ETYPE_BUTTON,
|
||||
ETYPE_SLIDER,
|
||||
ETYPE_TEXT,
|
||||
ETYPE_SPRITE,
|
||||
}
|
||||
|
||||
bitstruct ElemFlags : uint {
|
||||
bool updated : 0;
|
||||
bool is_new : 1; // element is new in the cache
|
||||
bool shown : 2; // element has been shown (drawn) this frame
|
||||
}
|
||||
|
||||
bitstruct ElemEvents : uint {
|
||||
bool key_press : 0;
|
||||
bool key_release : 1;
|
||||
bool key_repeat : 2;
|
||||
bool mouse_hover : 3;
|
||||
bool mouse_press : 4;
|
||||
bool mouse_release : 5;
|
||||
bool mouse_hold : 6;
|
||||
bool update : 7;
|
||||
bool text_input : 8;
|
||||
bool has_focus : 9;
|
||||
}
|
||||
|
||||
// element structure
|
||||
struct Elem {
|
||||
Id id;
|
||||
int tree_idx;
|
||||
ElemFlags flags;
|
||||
ElemEvents events;
|
||||
Rect bounds;
|
||||
Rect children_bounds;
|
||||
ElemType type;
|
||||
Layout layout;
|
||||
int z_index;
|
||||
union {
|
||||
ElemDiv div;
|
||||
ElemButton button;
|
||||
ElemSlider slider;
|
||||
ElemText text;
|
||||
ElemSprite sprite;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const uint MAX_ELEMENTS = 256;
|
||||
const uint MAX_COMMANDS = 2048;
|
||||
const uint STACK_STEP = 10;
|
||||
const uint ROOT_ID = 1;
|
||||
const uint TEXT_MAX = 64;
|
||||
|
||||
|
||||
// Tuple of Element pointers, used when it is useful to get both parent and child
|
||||
alias PElemTuple = pair::Pair{Elem*, Elem*};
|
||||
|
||||
// relationships between elements are stored in a tree, it stores just the ids
|
||||
alias IdTree = mtree::MTree{Id};
|
||||
|
||||
// elements themselves are kept in a cache
|
||||
alias ElemCache = cache::Cache{Id, Elem, MAX_ELEMENTS};
|
||||
|
||||
faultdef INVALID_SIZE, EVENT_UNSUPPORTED, WRONG_ELEMENT_TYPE, WRONG_ID;
|
||||
|
||||
struct InputData {
|
||||
InputEvents events;
|
||||
struct mouse {
|
||||
Point pos, delta;
|
||||
// mouse_down: bitmap of mouse buttons that are held
|
||||
// mouse_updated: bitmap of mouse buttons that have been updated
|
||||
// mouse_released = mouse_updated & ~mouse_down
|
||||
// mouse_pressed = mouse_updated & mouse_down
|
||||
MouseButtons down;
|
||||
MouseButtons updated;
|
||||
// scroll wheel
|
||||
Point scroll;
|
||||
}
|
||||
struct keyboard {
|
||||
char[TEXT_MAX] text;
|
||||
usz text_len;
|
||||
ModKeys modkeys;
|
||||
}
|
||||
}
|
||||
|
||||
struct Ctx {
|
||||
IdTree tree;
|
||||
ElemCache cache;
|
||||
CmdQueue cmd_queue;
|
||||
StyleMap styles;
|
||||
// total size in pixels of the context
|
||||
ushort width, height;
|
||||
Font font;
|
||||
SpriteAtlas sprite_atlas;
|
||||
|
||||
bool skip_frame;
|
||||
|
||||
bool has_focus;
|
||||
InputData input, current_input;
|
||||
|
||||
Id hover_id;
|
||||
Id focus_id;
|
||||
|
||||
Rect div_scissor; // the current div bounds used for scissor test
|
||||
int active_div; // tree node indicating the current active div
|
||||
}
|
||||
|
||||
// return a pointer to the parent of the current active div
|
||||
fn Elem*? Ctx.get_parent(&ctx)
|
||||
{
|
||||
Id parent_id = ctx.tree[ctx.active_div]!;
|
||||
Elem* parent = ctx.cache.search(parent_id)!;
|
||||
return parent;
|
||||
}
|
||||
|
||||
macro @bits(#a) => $typeof(#a).sizeof*8;
|
||||
macro Id.rotate_left(id, uint $n) => (id << $n) | (id >> (@bits(id) - $n));
|
||||
const uint GOLDEN_RATIO = 0x9E3779B9;
|
||||
|
||||
// generate an id combining the hashes of the parent id and the label
|
||||
// with the Cantor pairing function
|
||||
fn Id? Ctx.gen_id(&ctx, Id id2)
|
||||
{
|
||||
// FIXME: this is SHIT
|
||||
Id id1 = ctx.tree.get(ctx.active_div)!;
|
||||
// Mix the two IDs non-linearly
|
||||
Id mixed = id1 ^ id2.rotate_left(13);
|
||||
mixed ^= id1.rotate_left(7);
|
||||
mixed += GOLDEN_RATIO;
|
||||
return mixed;
|
||||
}
|
||||
|
||||
// compute the id from arguments and the line of the call
|
||||
macro Id @compute_id(...)
|
||||
{
|
||||
Id id = (Id)$$LINE.hash() ^ (Id)@str_hash($$FILE);
|
||||
$for var $i = 0; $i < $vacount; $i++:
|
||||
id ^= (Id)$vaexpr[$i].hash();
|
||||
$endfor
|
||||
return id;
|
||||
}
|
||||
|
||||
// get or push an element from the cache, return a pointer to it
|
||||
// resets all flags except is_new which is set accordingly
|
||||
fn PElemTuple? Ctx.get_elem(&ctx, Id id, ElemType type)
|
||||
{
|
||||
bool is_new;
|
||||
Elem* parent;
|
||||
Elem* elem;
|
||||
parent = ctx.get_parent() ?? &&(Elem){};
|
||||
|
||||
elem = ctx.cache.get_or_insert(&&(Elem){}, id, &is_new)!;
|
||||
elem.flags = (ElemFlags)0;
|
||||
elem.flags.is_new = is_new;
|
||||
elem.flags.shown = true;
|
||||
elem.id = id;
|
||||
elem.layout = {};
|
||||
if (is_new == false && elem.type != type) {
|
||||
return WRONG_ELEMENT_TYPE?;
|
||||
} else {
|
||||
elem.type = type;
|
||||
}
|
||||
elem.z_index = parent.z_index;
|
||||
elem.tree_idx = ctx.tree.add(ctx.active_div, id)!;
|
||||
return {elem, parent};
|
||||
}
|
||||
|
||||
// find an element, does not allocate a new one in cache
|
||||
// THIS HAS TO BE A MACRO SINCE IT RETURNS A POINTER TO A TEMPORARY VALUE
|
||||
macro Elem* Ctx.find_elem(&ctx, Id id)
|
||||
{
|
||||
Elem*? elem;
|
||||
elem = ctx.cache.search(id);
|
||||
if (catch elem) {
|
||||
return &&(Elem){};
|
||||
}
|
||||
return elem;
|
||||
}
|
||||
|
||||
fn Elem*? Ctx.get_active_div(&ctx)
|
||||
{
|
||||
Id id = ctx.tree.get(ctx.active_div)!;
|
||||
return ctx.cache.search(id);
|
||||
}
|
||||
|
||||
fn void? Ctx.init(&ctx, Allocator allocator)
|
||||
{
|
||||
ctx.tree.init(MAX_ELEMENTS, allocator);
|
||||
defer catch { (void)ctx.tree.free(); }
|
||||
|
||||
ctx.cache.init(allocator)!;
|
||||
defer catch { (void)ctx.cache.free(); }
|
||||
|
||||
ctx.cmd_queue.init(allocator::mem, MAX_COMMANDS);
|
||||
defer catch { (void)ctx.cmd_queue.free(); }
|
||||
|
||||
ctx.styles.init(allocator);
|
||||
ctx.styles.register_style(&DEFAULT_STYLE, @str_hash("default"));
|
||||
defer catch { ctx.styles.free(); }
|
||||
|
||||
ctx.active_div = 0;
|
||||
}
|
||||
|
||||
fn void Ctx.free(&ctx)
|
||||
{
|
||||
(void)ctx.tree.free();
|
||||
(void)ctx.cache.free();
|
||||
(void)ctx.cmd_queue.free();
|
||||
(void)ctx.font.free();
|
||||
(void)ctx.sprite_atlas.free();
|
||||
(void)ctx.styles.free();
|
||||
}
|
||||
|
||||
fn void? Ctx.frame_begin(&ctx)
|
||||
{
|
||||
// 1. Reset the active div
|
||||
// 2. Get the root element from the cache and update it
|
||||
ctx.active_div = 0;
|
||||
Elem* elem = ctx.get_elem(ROOT_ID, ETYPE_DIV)!.first;
|
||||
ctx.active_div = elem.tree_idx;
|
||||
// The root should have the updated flag only if the size of the window
|
||||
// was changed between frasmes, this propagates an element size recalculation
|
||||
// down the element tree
|
||||
elem.flags.updated = ctx.input.events.resize;
|
||||
// if the window has focus then the root element also has focus, no other
|
||||
// computation needed, child elements need to check the mouse positon and
|
||||
// other stuff
|
||||
//elem.flags.has_focus = ctx.has_focus;
|
||||
|
||||
elem.bounds = {0, 0, ctx.width, ctx.height};
|
||||
elem.z_index = 0;
|
||||
elem.div.scroll_x.enabled = false;
|
||||
elem.div.scroll_y.enabled = false;
|
||||
elem.layout.dir = ROW;
|
||||
elem.layout.anchor = TOP_LEFT;
|
||||
elem.layout.w = @exact(ctx.width);
|
||||
elem.layout.h = @exact(ctx.height);
|
||||
|
||||
ctx.div_scissor = elem.bounds;
|
||||
|
||||
ctx.skip_frame = false;
|
||||
}
|
||||
|
||||
fn void? Ctx.frame_end(&ctx)
|
||||
{
|
||||
Elem* root = ctx.get_active_div()!;
|
||||
if (root.id != ROOT_ID) {
|
||||
return WRONG_ID?;
|
||||
}
|
||||
|
||||
// 2. clear input fields
|
||||
ctx.input = ctx.current_input;
|
||||
ctx.current_input.events = {};
|
||||
ctx.current_input.mouse.scroll = {};
|
||||
ctx.current_input.mouse.updated = BTN_NONE;
|
||||
ctx.current_input.keyboard.text_len = 0;
|
||||
|
||||
// DO THE LAYOUT
|
||||
ctx.layout_element_tree()!;
|
||||
|
||||
foreach (idx, id : ctx.tree.elem_vec) {
|
||||
if (!ctx.tree.is_used((int)idx)) continue;
|
||||
Elem* c = ctx.find_elem(id);
|
||||
// reset events
|
||||
c.events = {};
|
||||
// reset shown flag
|
||||
// TODO: use shown_last_frame to avoid this loop entirely
|
||||
c.flags.shown = false;
|
||||
}
|
||||
|
||||
// Propagate input events to the right elements
|
||||
ctx.set_elem_events(ctx.hover_id);
|
||||
ctx.set_elem_events(ctx.focus_id);
|
||||
|
||||
|
||||
// 1. clear the tree
|
||||
ctx.tree.nuke();
|
||||
|
||||
// send atlas updates
|
||||
if (ctx.font.should_update) {
|
||||
ctx.push_update_atlas(&ctx.font.atlas)!;
|
||||
ctx.font.should_update = false;
|
||||
}
|
||||
if (ctx.sprite_atlas.should_update) {
|
||||
ctx.push_update_atlas(&ctx.sprite_atlas.atlas)!;
|
||||
ctx.sprite_atlas.should_update = false;
|
||||
}
|
||||
|
||||
// send skip frame request
|
||||
if (ctx.skip_frame) {
|
||||
ctx.cmd_queue.push({.type = CMD_REQ_SKIP_FRAME});
|
||||
}
|
||||
|
||||
// sort the command buffer by the z-index
|
||||
// FIXME: sorting the buffer fucks with scissor commands that have to be kept in place
|
||||
// TODO: instead of sorting at the end perform ordered inserts into the command buffer
|
||||
//sort::countingsort(ctx.cmd_queue, fn uint(Cmd c) => c.z_index+1);
|
||||
ctx.cmd_queue.sort()!;
|
||||
|
||||
// debug
|
||||
$if $feature(DEBUG_POINTER):
|
||||
// draw mouse position
|
||||
Cmd cmd = {
|
||||
.type = CMD_RECT,
|
||||
.z_index = int.max-1, // hopefully over everything else
|
||||
.rect.rect = {
|
||||
.x = ctx.input.mouse.pos.x - 2,
|
||||
.y = ctx.input.mouse.pos.y - 2,
|
||||
.w = 4,
|
||||
.h = 4,
|
||||
},
|
||||
.rect.color = 0xff00ffffu.to_rgba()
|
||||
};
|
||||
ctx.cmd_queue.push(cmd);
|
||||
$endif
|
||||
}
|
||||
|
||||
|
||||
macro bool Ctx.is_hovered(&ctx, Elem *elem) => ctx.input.mouse.pos.in_rect(elem.bounds);
|
||||
|
||||
// Check if the element is hovered and/or focused, if it is update the context ids.
|
||||
// The order in which the elements are passed to this function is not relevant
|
||||
fn void Ctx.update_hover_and_focus(&ctx, Elem* elem)
|
||||
{
|
||||
bool hover = ctx.is_hovered(elem);
|
||||
bool focus = ctx.focus_id == elem.id || (hover && ctx.is_mouse_pressed(BTN_ANY));
|
||||
|
||||
if (hover) {
|
||||
Elem* prev_hover = ctx.find_elem(ctx.hover_id);
|
||||
bool different = prev_hover.id != elem.id;
|
||||
bool still_hovered = ctx.is_hovered(prev_hover);
|
||||
bool shown = prev_hover.flags.shown;
|
||||
bool above = prev_hover.z_index > elem.z_index;
|
||||
|
||||
hover = !(different && still_hovered && shown && above);
|
||||
}
|
||||
|
||||
if (focus) {
|
||||
Elem* prev_focus = ctx.find_elem(ctx.hover_id);
|
||||
bool different = prev_focus.id != elem.id;
|
||||
bool shown = prev_focus.flags.shown;
|
||||
bool above = prev_focus.z_index > elem.z_index;
|
||||
|
||||
focus = !(different && shown && above);
|
||||
}
|
||||
|
||||
if (hover) ctx.hover_id = elem.id;
|
||||
if (focus) ctx.focus_id = elem.id;
|
||||
}
|
||||
|
||||
// FIXME: this does not work with touch
|
||||
fn void Ctx.set_elem_events(&ctx, Id id)
|
||||
{
|
||||
bool hover = id == ctx.hover_id;
|
||||
bool focus = id == ctx.focus_id;
|
||||
Elem* e = ctx.find_elem(id);
|
||||
|
||||
e.events = {
|
||||
.has_focus = focus,
|
||||
.mouse_hover = hover,
|
||||
.mouse_press = hover && focus && ctx.is_mouse_pressed(BTN_ANY),
|
||||
.mouse_release = hover && focus && ctx.is_mouse_released(BTN_ANY),
|
||||
.mouse_hold = hover && focus && ctx.is_mouse_down(BTN_ANY),
|
||||
.key_press = focus && ctx.input.events.key_press,
|
||||
.key_release = focus && ctx.input.events.key_release,
|
||||
.key_repeat = focus && ctx.input.events.key_repeat,
|
||||
.text_input = focus && (ctx.input.keyboard.text_len || ctx.input.keyboard.modkeys & KMOD_TXT),
|
||||
};
|
||||
}
|
||||
227
src/font.c3
Normal file
227
src/font.c3
Normal file
@ -0,0 +1,227 @@
|
||||
module ugui::font;
|
||||
|
||||
import schrift;
|
||||
import std::collections::map;
|
||||
import std::core::mem;
|
||||
import std::core::mem::allocator;
|
||||
import std::io;
|
||||
import std::ascii;
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
// CODEPOINT //
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
|
||||
// unicode code point, different type for a different hash
|
||||
alias Codepoint = uint;
|
||||
|
||||
//macro uint Codepoint.hash(self) => ((uint)self).hash();
|
||||
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
// FONT ATLAS //
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
|
||||
/* width and height of a glyph contain the kering advance
|
||||
* (u,v)
|
||||
* +-------------*---+ -
|
||||
* | ^ | | ^
|
||||
* | |oy | | |
|
||||
* | v | | |
|
||||
* | .ii. | | |
|
||||
* | @@@@@@. | | |
|
||||
* | V@Mio@@o | | |
|
||||
* | :i. V@V | | h
|
||||
* | :oM@@M | | |
|
||||
* | :@@@MM@M | | |
|
||||
* | @@o o@M | | |
|
||||
* |<->:@@. M@M | | |
|
||||
* |ox @@@o@@@@ | | |
|
||||
* | :M@@V:@@.| | v
|
||||
* +-------------*---+ -
|
||||
* |<---- w ---->|
|
||||
* |<------ adv ---->|
|
||||
*/
|
||||
struct Glyph {
|
||||
Codepoint code;
|
||||
ushort u, v;
|
||||
ushort w, h;
|
||||
short adv, ox, oy;
|
||||
}
|
||||
|
||||
const uint FONT_CACHED = 255;
|
||||
alias GlyphTable = map::HashMap{Codepoint, Glyph};
|
||||
|
||||
faultdef TTF_LOAD_FAILED, MISSING_GLYPH, BAD_GLYPH_METRICS, RENDER_ERROR;
|
||||
|
||||
struct Font {
|
||||
schrift::Sft sft;
|
||||
String path;
|
||||
Id id; // font id, same as atlas id
|
||||
GlyphTable table;
|
||||
|
||||
float size;
|
||||
float ascender, descender, linegap; // Line Metrics
|
||||
Atlas atlas;
|
||||
bool should_update; // should send update_atlas command, resets at frame_end()
|
||||
}
|
||||
|
||||
macro Rect Glyph.bounds(&g) => {.x = g.ox, .y = g.oy, .w = g.w, .h = g.h};
|
||||
macro Rect Glyph.uv(&g) => {.x = g.u, .y = g.v, .w = g.w, .h = g.h};
|
||||
|
||||
<*
|
||||
@param [&inout] font
|
||||
@param [in] name
|
||||
@param [&in] path
|
||||
@require height > 0, scale > 0: "height and scale must be positive non-zero"
|
||||
*>
|
||||
fn void? Font.load(&font, Allocator allocator, String name, ZString path, uint height, float scale)
|
||||
{
|
||||
font.table.init(allocator, capacity: FONT_CACHED);
|
||||
font.id = name.hash();
|
||||
|
||||
font.size = height*scale;
|
||||
|
||||
font.sft = {
|
||||
.xScale = (double)font.size,
|
||||
.yScale = (double)font.size,
|
||||
.flags = schrift::SFT_DOWNWARD_Y,
|
||||
};
|
||||
|
||||
font.sft.font = schrift::loadfile(path);
|
||||
if (font.sft.font == null) {
|
||||
font.table.free();
|
||||
return TTF_LOAD_FAILED?;
|
||||
}
|
||||
|
||||
schrift::SftLMetrics lmetrics;
|
||||
schrift::lmetrics(&font.sft, &lmetrics);
|
||||
font.ascender = (float)lmetrics.ascender;
|
||||
font.descender = (float)lmetrics.descender;
|
||||
font.linegap = (float)lmetrics.lineGap;
|
||||
//io::printfn("ascender:%d, descender:%d, linegap:%d", font.ascender, font.descender, font.linegap);
|
||||
|
||||
// TODO: allocate buffer based on FONT_CACHED and the size of a sample letter
|
||||
// like the letter 'A'
|
||||
ushort size = (ushort)font.size*(ushort)($$sqrt((float)FONT_CACHED));
|
||||
font.atlas.new(font.id, ATLAS_GRAYSCALE, size, size)!;
|
||||
|
||||
// preallocate the ASCII range
|
||||
for (char c = ' '; c < '~'; c++) {
|
||||
// FIXME: without @inline, this crashes with O1 or greater
|
||||
font.get_glyph((Codepoint)c) @inline!;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] font
|
||||
*>
|
||||
fn Glyph*? Font.get_glyph(&font, Codepoint code)
|
||||
{
|
||||
Glyph*? gp;
|
||||
gp = font.table.get_ref(code);
|
||||
|
||||
if (catch excuse = gp) {
|
||||
if (excuse != NOT_FOUND) {
|
||||
return excuse?;
|
||||
}
|
||||
} else {
|
||||
return gp;
|
||||
}
|
||||
|
||||
|
||||
// missing glyph, render and place into an atlas
|
||||
Glyph glyph;
|
||||
|
||||
schrift::SftGlyph gid;
|
||||
schrift::SftGMetrics gmtx;
|
||||
|
||||
if (schrift::lookup(&font.sft, (SftUChar)code, &gid) < 0) {
|
||||
return MISSING_GLYPH?;
|
||||
}
|
||||
|
||||
if (schrift::gmetrics(&font.sft, gid, &gmtx) < 0) {
|
||||
return BAD_GLYPH_METRICS?;
|
||||
}
|
||||
|
||||
schrift::SftImage img = {
|
||||
.width = gmtx.minWidth,
|
||||
.height = gmtx.minHeight,
|
||||
};
|
||||
char[] pixels = mem::new_array(char, (usz)img.width * img.height);
|
||||
img.pixels = pixels;
|
||||
if (schrift::render(&font.sft, gid, img) < 0) {
|
||||
return RENDER_ERROR?;
|
||||
}
|
||||
|
||||
glyph.code = code;
|
||||
glyph.w = (ushort)img.width;
|
||||
glyph.h = (ushort)img.height;
|
||||
glyph.ox = (short)gmtx.leftSideBearing;
|
||||
glyph.oy = (short)gmtx.yOffset;
|
||||
glyph.adv = (short)gmtx.advanceWidth;
|
||||
//io::printfn("code=%c, w=%d, h=%d, ox=%d, oy=%d, adv=%d",
|
||||
// glyph.code, glyph.w, glyph.h, glyph.ox, glyph.oy, glyph.adv);
|
||||
|
||||
Point uv = font.atlas.place(pixels, glyph.w, glyph.h, (ushort)img.width)!;
|
||||
glyph.u = uv.x;
|
||||
glyph.v = uv.y;
|
||||
|
||||
mem::free(pixels);
|
||||
|
||||
font.table.set(code, glyph);
|
||||
|
||||
font.should_update = true;
|
||||
return font.table.get_ref(code);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] font
|
||||
*>
|
||||
fn void Font.free(&font)
|
||||
{
|
||||
font.atlas.free();
|
||||
font.table.free();
|
||||
schrift::freefont(font.sft.font);
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
// FONT LOAD AND QUERY //
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
|
||||
module ugui;
|
||||
|
||||
<*
|
||||
@param [&inout] ctx
|
||||
@param [in] name
|
||||
@param [&in] path
|
||||
@require height > 0, scale > 0: "height and scale must be positive non-zero"
|
||||
*>
|
||||
fn void? Ctx.load_font(&ctx, Allocator allocator, String name, ZString path, uint height, float scale = 1.0)
|
||||
{
|
||||
return ctx.font.load(allocator, name, path, height, scale);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ctx
|
||||
@param [in] label
|
||||
*>
|
||||
// TODO: check if the font is present in the context
|
||||
fn Id Ctx.get_font_id(&ctx, String label) => (Id)label.hash();
|
||||
|
||||
<*
|
||||
@param [&in] ctx
|
||||
@param [in] name
|
||||
*>
|
||||
fn Atlas*? Ctx.get_font_atlas(&ctx, String name)
|
||||
{
|
||||
// TODO: use the font name, for now there is only one font
|
||||
if (name.hash() != ctx.font.id) {
|
||||
return WRONG_ID?;
|
||||
}
|
||||
|
||||
return &ctx.font.atlas;
|
||||
}
|
||||
|
||||
<* @param [&in] font *>
|
||||
fn int Font.line_height(&font) => (int)(font.ascender - font.descender + (float)0.5);
|
||||
237
src/input.c3
Normal file
237
src/input.c3
Normal file
@ -0,0 +1,237 @@
|
||||
module ugui;
|
||||
|
||||
import std::io;
|
||||
import std::math;
|
||||
import std::core::string;
|
||||
|
||||
|
||||
bitstruct InputEvents : uint {
|
||||
bool resize : 0; // window size was changed
|
||||
bool change_focus : 1; // window focus changed
|
||||
bool mouse_move : 2; // mouse was moved
|
||||
bool mouse_btn : 3; // mouse button pressed or released
|
||||
bool mouse_scroll : 4; // mouse scroll wheel. x or y
|
||||
bool text_input : 5;
|
||||
bool mod_key : 6;
|
||||
bool key_press : 7;
|
||||
bool key_release : 8;
|
||||
bool key_repeat : 9;
|
||||
}
|
||||
|
||||
bitstruct MouseButtons : uint {
|
||||
bool btn_left : 0;
|
||||
bool btn_middle : 1;
|
||||
bool btn_right : 2;
|
||||
bool btn_4 : 3;
|
||||
bool btn_5 : 4;
|
||||
}
|
||||
|
||||
// FIXME: all of these names were prefixed with key_ idk if this is better,
|
||||
// if it is remove the prefix on MouseButtons as well
|
||||
// Modifier Keys, intended as any key that is not text
|
||||
bitstruct ModKeys : uint {
|
||||
bool lshift : 0;
|
||||
bool rshift : 1;
|
||||
bool lctrl : 2;
|
||||
bool rctrl : 3;
|
||||
bool lalt : 4;
|
||||
bool ralt : 5;
|
||||
bool lgui : 6;
|
||||
bool rgui : 7;
|
||||
bool num : 8;
|
||||
bool caps : 9;
|
||||
bool mode : 10;
|
||||
bool scroll : 11;
|
||||
bool bkspc : 12;
|
||||
bool del : 13;
|
||||
bool home : 14;
|
||||
bool end : 15;
|
||||
// arrow keys
|
||||
bool up : 16;
|
||||
bool down : 17;
|
||||
bool left : 18;
|
||||
bool right : 19;
|
||||
}
|
||||
|
||||
const ModKeys KMOD_CTRL = {.lctrl = true, .rctrl = true};
|
||||
const ModKeys KMOD_SHIFT = {.lshift = true, .rshift = true};
|
||||
const ModKeys KMOD_ALT = {.lalt = true, .ralt = true};
|
||||
const ModKeys KMOD_GUI = {.lgui = true, .rgui = true};
|
||||
const ModKeys KMOD_TXT = {.bkspc = true, .del = true}; // modkeys that act like text input
|
||||
const ModKeys KMOD_NONE = {};
|
||||
const ModKeys KMOD_ANY = (ModKeys)(ModKeys.inner.max);
|
||||
|
||||
const MouseButtons BTN_NONE = {};
|
||||
const MouseButtons BTN_ANY = (MouseButtons)(MouseButtons.inner.max);
|
||||
const MouseButtons BTN_LEFT = {.btn_left = true};
|
||||
const MouseButtons BTN_MIDDLE = {.btn_middle = true};
|
||||
const MouseButtons BTN_RIGHT = {.btn_right = true};
|
||||
const MouseButtons BTN_4 = {.btn_4 = true};
|
||||
const MouseButtons BTN_5 = {.btn_5 = true};
|
||||
|
||||
const ModKeys KEY_ANY = (ModKeys)(ModKeys.inner.max);
|
||||
|
||||
<* @param [&inout] ctx *>
|
||||
fn bool Ctx.check_key_combo(&ctx, ModKeys mod, String ...keys)
|
||||
{
|
||||
bool is_mod = (bool)(ctx.current_input.keyboard.modkeys & mod);
|
||||
bool is_keys = true;
|
||||
String haystack = (String)ctx.get_keys();
|
||||
foreach (needle: keys) {
|
||||
is_keys = is_keys && haystack.contains(needle);
|
||||
}
|
||||
return is_mod && is_keys;
|
||||
}
|
||||
|
||||
// Window size was changed
|
||||
<* @param [&inout] ctx *>
|
||||
fn void? Ctx.input_window_size(&ctx, short width, short height)
|
||||
{
|
||||
if (width <= 0 || height <= 0) {
|
||||
return INVALID_SIZE?;
|
||||
}
|
||||
ctx.current_input.events.resize = ctx.width != width || ctx.height != height;
|
||||
ctx.width = width;
|
||||
ctx.height = height;
|
||||
if (ctx.current_input.events.resize) ctx.skip_frame = true;
|
||||
}
|
||||
|
||||
// Window gained/lost focus
|
||||
<* @param [&inout] ctx *>
|
||||
fn void Ctx.input_changefocus(&ctx, bool has_focus)
|
||||
{
|
||||
// FIXME: raylib only has an API to query the focus status so we have to
|
||||
// update the input flag only if the focus changed
|
||||
ctx.current_input.events.change_focus = ctx.has_focus != has_focus;
|
||||
ctx.has_focus = has_focus;
|
||||
}
|
||||
|
||||
// NOTE: all of these refer to the previous frame's data
|
||||
macro Ctx.mouse_pressed(&ctx) => ctx.input.mouse.updated & ctx.input.mouse.down;
|
||||
macro Ctx.mouse_released(&ctx) => ctx.input.mouse.updated & ~ctx.input.mouse.down;
|
||||
macro Ctx.mouse_down(&ctx) => ctx.input.mouse.down;
|
||||
macro Ctx.is_mouse_pressed(&ctx, MouseButtons btn) => (ctx.mouse_pressed() & btn) != BTN_NONE;
|
||||
macro Ctx.is_mouse_released(&ctx, MouseButtons btn) => (ctx.mouse_released() & btn) != BTN_NONE;
|
||||
macro Ctx.is_mouse_down(&ctx, MouseButtons btn) => (ctx.mouse_down() & btn) != BTN_NONE;
|
||||
|
||||
// Mouse Buttons down
|
||||
<* @param [&inout] ctx *>
|
||||
fn void Ctx.input_mouse_button(&ctx, MouseButtons buttons)
|
||||
{
|
||||
ctx.current_input.mouse.updated = ctx.current_input.mouse.down ^ buttons;
|
||||
ctx.current_input.mouse.down = buttons;
|
||||
ctx.current_input.events.mouse_btn = ctx.current_input.mouse.down != BTN_NONE || ctx.current_input.mouse.updated != BTN_NONE;
|
||||
}
|
||||
|
||||
// Mouse was moved, report absolute position
|
||||
<* @param [&inout] ctx *>
|
||||
fn void Ctx.input_mouse_abs(&ctx, short x, short y)
|
||||
{
|
||||
ctx.current_input.mouse.pos.x = math::clamp(x, (short)0, ctx.width);
|
||||
ctx.current_input.mouse.pos.y = math::clamp(y, (short)0, ctx.height);
|
||||
|
||||
short dx, dy;
|
||||
dx = x - ctx.current_input.mouse.pos.x;
|
||||
dy = y - ctx.current_input.mouse.pos.y;
|
||||
|
||||
ctx.current_input.mouse.delta.x = dx;
|
||||
ctx.current_input.mouse.delta.y = dy;
|
||||
|
||||
ctx.current_input.events.mouse_move = dx != 0 || dy != 0;
|
||||
}
|
||||
|
||||
// Mouse was moved, report relative motion
|
||||
<* @param [&inout] ctx *>
|
||||
fn void Ctx.input_mouse_delta(&ctx, short dx, short dy)
|
||||
{
|
||||
ctx.current_input.mouse.delta.x = dx;
|
||||
ctx.current_input.mouse.delta.y = dy;
|
||||
|
||||
short mx, my;
|
||||
mx = ctx.current_input.mouse.pos.x + dx;
|
||||
my = ctx.current_input.mouse.pos.y + dy;
|
||||
|
||||
ctx.current_input.mouse.pos.x = math::clamp(mx, (short)0, ctx.width);
|
||||
ctx.current_input.mouse.pos.y = math::clamp(my, (short)0, ctx.height);
|
||||
|
||||
ctx.current_input.events.mouse_move = dx != 0 || dy != 0;
|
||||
}
|
||||
|
||||
<* @param [&inout] ctx *>
|
||||
fn void Ctx.input_mouse_wheel(&ctx, short x, short y, float scale = 1.0)
|
||||
{
|
||||
ctx.current_input.mouse.scroll.x = (short)((float)-x*scale);
|
||||
ctx.current_input.mouse.scroll.y = (short)((float)-y*scale);
|
||||
ctx.current_input.events.mouse_scroll = x !=0 || y != 0;
|
||||
}
|
||||
|
||||
<* @param [&inout] ctx *>
|
||||
fn void Ctx.input_key_press(&ctx)
|
||||
{
|
||||
ctx.current_input.events.key_press = true;
|
||||
}
|
||||
|
||||
<* @param [&inout] ctx *>
|
||||
fn void Ctx.input_key_release(&ctx)
|
||||
{
|
||||
ctx.current_input.events.key_release = true;
|
||||
}
|
||||
|
||||
<* @param [&inout] ctx *>
|
||||
fn void Ctx.input_key_repeat(&ctx)
|
||||
{
|
||||
ctx.current_input.events.key_repeat = true;
|
||||
}
|
||||
|
||||
// append utf-8 encoded text to the context text input
|
||||
<* @param [&inout] ctx *>
|
||||
fn void Ctx.input_text_utf8(&ctx, char[] text)
|
||||
{
|
||||
if (text == "") return;
|
||||
|
||||
usz remaining = ctx.current_input.keyboard.text.len - ctx.current_input.keyboard.text_len;
|
||||
usz len = text.len > remaining ? remaining : text.len;
|
||||
char[] s = ctx.current_input.keyboard.text[ctx.current_input.keyboard.text_len ..];
|
||||
s[..len-1] = text[..len-1];
|
||||
ctx.current_input.keyboard.text_len += len;
|
||||
ctx.current_input.events.text_input = true;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] ctx
|
||||
@param [in] text
|
||||
*>
|
||||
fn void? Ctx.input_text_unicode(&ctx, uint[] text)
|
||||
{
|
||||
if (text.len == 0) return;
|
||||
|
||||
usz remaining = ctx.current_input.keyboard.text.len - ctx.current_input.keyboard.text_len;
|
||||
char[] s = ctx.current_input.keyboard.text[ctx.current_input.keyboard.text_len ..];
|
||||
|
||||
usz off = conv::utf32to8(text, s)!;
|
||||
ctx.current_input.keyboard.text_len += off;
|
||||
|
||||
ctx.current_input.events.text_input = true;
|
||||
}
|
||||
|
||||
<* @param [&inout] ctx *>
|
||||
fn void Ctx.input_char(&ctx, char c)
|
||||
{
|
||||
char[1] b = {c};
|
||||
ctx.input_text_utf8(b[..]);
|
||||
}
|
||||
|
||||
fn String Ctx.get_keys(&ctx) => (String)ctx.input.keyboard.text[:ctx.input.keyboard.text_len];
|
||||
fn ModKeys Ctx.get_mod(&ctx) => ctx.input.keyboard.modkeys;
|
||||
|
||||
// Modifier keys, like control or backspace
|
||||
<* @param [&inout] ctx *>
|
||||
fn void Ctx.input_mod_keys(&ctx, ModKeys modkeys, bool set)
|
||||
{
|
||||
if (set) {
|
||||
ctx.current_input.keyboard.modkeys |= modkeys;
|
||||
} else {
|
||||
ctx.current_input.keyboard.modkeys &= ~modkeys;
|
||||
}
|
||||
ctx.current_input.events.mod_key = (uint)ctx.current_input.keyboard.modkeys != 0;
|
||||
}
|
||||
353
src/layout.c3
Normal file
353
src/layout.c3
Normal file
@ -0,0 +1,353 @@
|
||||
module ugui;
|
||||
|
||||
import std::math;
|
||||
import std::io;
|
||||
|
||||
enum LayoutDirection {
|
||||
ROW,
|
||||
COLUMN,
|
||||
}
|
||||
|
||||
enum Anchor {
|
||||
TOP_LEFT,
|
||||
LEFT,
|
||||
BOTTOM_LEFT,
|
||||
BOTTOM,
|
||||
BOTTOM_RIGHT,
|
||||
RIGHT,
|
||||
TOP_RIGHT,
|
||||
TOP,
|
||||
CENTER
|
||||
}
|
||||
|
||||
struct Layout {
|
||||
Size w, h; // size of the CONTENT, does not include margin, border and padding
|
||||
struct children { // the children size includes the children's margin/border/pading
|
||||
Size w, h;
|
||||
}
|
||||
TextSize text;
|
||||
ushort grow_children;
|
||||
short occupied;
|
||||
Point origin;
|
||||
Point scroll_offset;
|
||||
// false: the element is laid out according to the parent
|
||||
// true: the element is laid out separate from all other children and the relative position to
|
||||
// the parent is the .origin field
|
||||
bool absolute;
|
||||
LayoutDirection dir; // the direction the children are laid out
|
||||
Anchor anchor; // how the children are positioned
|
||||
Rect content_offset; // combined effect of margin, border and padding
|
||||
}
|
||||
|
||||
// Returns the width and height of a @FIT() element based on it's wanted size (min/max)
|
||||
// and the content size, this function is used to both update the parent's children size and
|
||||
// give the dimensions of a fit element
|
||||
// TODO: test and cleanup this function
|
||||
macro Point Layout.get_dimensions(&el)
|
||||
{
|
||||
Point dim;
|
||||
// if the direction is ROW then the text is placed horizontally with the children
|
||||
if (el.dir == ROW) {
|
||||
Size content_width = el.children.w + el.text.width;
|
||||
Size width = el.w.combine(content_width);
|
||||
short final_width = width.greater();
|
||||
|
||||
short text_height;
|
||||
if (el.text.area != 0) {
|
||||
short text_width = (@exact(final_width) - el.children.w).combine(el.text.width).min;
|
||||
text_height = @exact((short)(el.text.area / text_width)).combine(el.text.height).min;
|
||||
}
|
||||
|
||||
Size content_height = el.children.h.comb_max(@exact(text_height));
|
||||
Size height = el.h.combine(content_height);
|
||||
short final_height = height.greater();
|
||||
|
||||
dim = {
|
||||
.x = final_width + el.content_offset.x + el.content_offset.w,
|
||||
.y = final_height + el.content_offset.y + el.content_offset.h,
|
||||
};
|
||||
} else {
|
||||
// if the direction is COLUMN the text and children are one on top of the other
|
||||
Size content_width = el.children.w.comb_max(el.text.width);
|
||||
Size width = el.w.combine(content_width);
|
||||
short final_width = width.greater();
|
||||
|
||||
short text_height;
|
||||
if (el.text.area != 0) {
|
||||
short text_width = @exact(final_width).combine(el.text.width).min;
|
||||
text_height = @exact((short)(el.text.area / text_width)).combine(el.text.height).min;
|
||||
}
|
||||
|
||||
Size content_height = el.children.h + @exact(text_height);
|
||||
Size height = el.h.combine(content_height);
|
||||
short final_height = height.greater();
|
||||
|
||||
dim = {
|
||||
.x = final_width + el.content_offset.x + el.content_offset.w,
|
||||
.y = final_height + el.content_offset.y + el.content_offset.h,
|
||||
};
|
||||
}
|
||||
|
||||
// GROSS HACK FOR EXACT DIMENSIONS
|
||||
if (el.w.@is_exact()) dim.x = el.w.min + el.content_offset.x + el.content_offset.w;
|
||||
if (el.h.@is_exact()) dim.y = el.h.min + el.content_offset.y + el.content_offset.h;
|
||||
|
||||
// GROSS HACK FOR GROW DIMENSIONS
|
||||
// FIXME: does this always work?
|
||||
if (el.w.@is_grow()) dim.x = 0;
|
||||
if (el.h.@is_grow()) dim.y = 0;
|
||||
|
||||
return dim;
|
||||
}
|
||||
|
||||
// The content space of the element
|
||||
macro Point Elem.content_space(&e)
|
||||
{
|
||||
return {
|
||||
.x = (short)max(e.bounds.w - e.layout.content_offset.x - e.layout.content_offset.w, 0),
|
||||
.y = (short)max(e.bounds.h - e.layout.content_offset.y - e.layout.content_offset.h, 0),
|
||||
};
|
||||
}
|
||||
|
||||
// Update the parent element's children size
|
||||
fn void update_parent_size(Elem* child, Elem* parent)
|
||||
{
|
||||
Layout* cl = &child.layout;
|
||||
Layout* pl = &parent.layout;
|
||||
|
||||
// if the element has absolute position do not update the parent
|
||||
if (cl.absolute) return;
|
||||
|
||||
Point child_size = cl.get_dimensions();
|
||||
|
||||
switch (pl.dir) {
|
||||
case ROW: // on rows grow the ch width by the child width and only grow ch height if it exceeds
|
||||
pl.children.w += @exact(child_size.x);
|
||||
pl.children.h = pl.children.h.comb_max(@exact(child_size.y));
|
||||
if (child.layout.w.@is_grow()) parent.layout.grow_children++;
|
||||
case COLUMN: // do the opposite on column
|
||||
pl.children.w = pl.children.w.comb_max(@exact(child_size.x));
|
||||
pl.children.h += @exact(child_size.y);
|
||||
if (child.layout.h.@is_grow()) parent.layout.grow_children++;
|
||||
}
|
||||
}
|
||||
|
||||
macro Rect Elem.content_bounds(&elem) => elem.bounds.pad(elem.layout.content_offset);
|
||||
|
||||
// Assign the width and height of an element in the directions that it doesn't need to grow
|
||||
fn void resolve_dimensions(Elem* e, Elem* p)
|
||||
{
|
||||
Layout* el = &e.layout;
|
||||
Layout* pl = &p.layout;
|
||||
|
||||
Point elem_dimensions = el.get_dimensions();
|
||||
e.bounds.w = elem_dimensions.x;
|
||||
e.bounds.h = elem_dimensions.y;
|
||||
|
||||
// if the element has absolute position do not update the parent
|
||||
if (el.absolute) return;
|
||||
|
||||
switch (pl.dir) {
|
||||
case ROW:
|
||||
if (!el.w.@is_grow()) pl.occupied += e.bounds.w;
|
||||
case COLUMN:
|
||||
if (!el.h.@is_grow()) pl.occupied += e.bounds.h;
|
||||
}
|
||||
}
|
||||
|
||||
fn void resolve_grow_elements(Elem* e, Elem* p)
|
||||
{
|
||||
// WIDTH
|
||||
if (e.layout.w.@is_grow()) {
|
||||
if (e.layout.absolute) { // absolute children do not need to share space
|
||||
e.bounds.w = p.content_space().x;
|
||||
} else if (p.layout.dir == ROW) { // grow along the axis, divide the parent size
|
||||
short slot = (short)max(((p.content_space().x - p.layout.occupied) / p.layout.grow_children), 0);
|
||||
e.bounds.w = slot;
|
||||
p.layout.grow_children--;
|
||||
p.layout.occupied += slot;
|
||||
} else if (p.layout.dir == COLUMN) { // grow across the layout axis, inherit width of the parent
|
||||
e.bounds.w = p.content_space().x;
|
||||
}
|
||||
}
|
||||
|
||||
// HEIGHT
|
||||
if (e.layout.h.@is_grow()) {
|
||||
if (e.layout.absolute) { // absolute children do not need to share space
|
||||
e.bounds.h = p.content_space().y;
|
||||
} else if (p.layout.dir == COLUMN) { // grow along the axis, divide the parent size
|
||||
short slot = (short)max(((p.content_space().y - p.layout.occupied) / p.layout.grow_children), 0);
|
||||
e.bounds.h = slot;
|
||||
p.layout.grow_children--;
|
||||
p.layout.occupied += slot;
|
||||
} else if (p.layout.dir == ROW) { // grow across the layout axis, inherit width of the parent
|
||||
e.bounds.h = p.content_space().y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void resolve_placement(Elem* c, Elem* p)
|
||||
{
|
||||
Layout* pl = &p.layout;
|
||||
Layout* cl = &c.layout;
|
||||
|
||||
Point off = {
|
||||
.x = p.bounds.x + pl.origin.x + pl.content_offset.x,
|
||||
.y = p.bounds.y + pl.origin.y + pl.content_offset.y,
|
||||
};
|
||||
|
||||
// if the element has absolute position assign the origin and do not update the parent
|
||||
if (cl.absolute) {
|
||||
c.bounds.x = p.bounds.x + pl.content_offset.x + cl.origin.x;
|
||||
c.bounds.y = p.bounds.y + pl.content_offset.y + cl.origin.y;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (pl.anchor) {
|
||||
case TOP_LEFT:
|
||||
c.bounds.x = off.x;
|
||||
c.bounds.y = off.y;
|
||||
case LEFT:
|
||||
c.bounds.x = off.x;
|
||||
c.bounds.y = off.y + p.content_space().y/2;
|
||||
if (pl.dir == COLUMN) {
|
||||
c.bounds.y -= pl.occupied/2;
|
||||
} else if (pl.dir == ROW) {
|
||||
c.bounds.y -= c.bounds.h/2;
|
||||
}
|
||||
case BOTTOM_LEFT:
|
||||
c.bounds.x = off.x;
|
||||
c.bounds.y = off.y + p.content_space().y ;
|
||||
if (pl.dir == COLUMN) {
|
||||
c.bounds.y -= pl.occupied;
|
||||
} else if (pl.dir == ROW) {
|
||||
c.bounds.y -= c.bounds.h;
|
||||
}
|
||||
case BOTTOM:
|
||||
c.bounds.x = off.x + p.content_space().x/2;
|
||||
c.bounds.y = off.y + p.content_space().y;
|
||||
if (pl.dir == COLUMN) {
|
||||
c.bounds.y -= pl.occupied;
|
||||
c.bounds.x -= c.bounds.w/2;
|
||||
} else if (pl.dir == ROW) {
|
||||
c.bounds.y -= c.bounds.h;
|
||||
c.bounds.x -= pl.occupied/2;
|
||||
}
|
||||
case BOTTOM_RIGHT:
|
||||
c.bounds.x = off.x + p.content_space().x;
|
||||
c.bounds.y = off.y + p.content_space().y;
|
||||
if (pl.dir == COLUMN) {
|
||||
c.bounds.y -= pl.occupied;
|
||||
c.bounds.x -= c.bounds.w;
|
||||
} else if (pl.dir == ROW) {
|
||||
c.bounds.y -= c.bounds.h;
|
||||
c.bounds.x -= pl.occupied;
|
||||
}
|
||||
case RIGHT:
|
||||
c.bounds.x = off.x + p.content_space().x;
|
||||
c.bounds.y = off.y + p.content_space().y/2;
|
||||
if (pl.dir == COLUMN) {
|
||||
c.bounds.y -= pl.occupied/2;
|
||||
c.bounds.x -= c.bounds.w;
|
||||
} else if (pl.dir == ROW) {
|
||||
c.bounds.y -= c.bounds.h/2;
|
||||
c.bounds.x -= pl.occupied;
|
||||
}
|
||||
case TOP_RIGHT:
|
||||
c.bounds.x = off.x + p.content_space().x;
|
||||
c.bounds.y = off.y;
|
||||
if (pl.dir == COLUMN) {
|
||||
c.bounds.x -= c.bounds.w;
|
||||
} else if (pl.dir == ROW) {
|
||||
c.bounds.x -= pl.occupied;
|
||||
}
|
||||
case TOP:
|
||||
c.bounds.x = off.x + p.content_space().x/2;
|
||||
c.bounds.y = off.y;
|
||||
if (pl.dir == COLUMN) {
|
||||
c.bounds.x -= c.bounds.w/2;
|
||||
} else if (pl.dir == ROW) {
|
||||
c.bounds.x -= pl.occupied/2;
|
||||
}
|
||||
case CENTER:
|
||||
c.bounds.x = off.x + p.content_space().x/2;
|
||||
c.bounds.y = off.y + p.content_space().y/2;
|
||||
if (pl.dir == COLUMN) {
|
||||
c.bounds.x -= c.bounds.w/2;
|
||||
c.bounds.y -= pl.occupied/2;
|
||||
} else if (pl.dir == ROW) {
|
||||
c.bounds.x -= pl.occupied/2;
|
||||
c.bounds.y -= c.bounds.h/2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
switch (pl.dir) {
|
||||
case ROW:
|
||||
pl.origin.x += c.bounds.w;
|
||||
case COLUMN:
|
||||
pl.origin.y += c.bounds.h;
|
||||
default: unreachable("unknown layout direction");
|
||||
}
|
||||
|
||||
// update the parent children_bounds
|
||||
// FIXME: this causes scollbars to flicker in/out during resize because the current frames children_bounds are updated
|
||||
// with the previous' frame child bounds. It would be better to implement another pass during layout.
|
||||
// FIXME: this long way around to compute the children bounds works and reduces flickering, but it is very ugly
|
||||
Rect ncb = c.children_bounds;
|
||||
ncb.x = c.bounds.x;
|
||||
ncb.y = c.bounds.y;
|
||||
Rect cb = containing_rect(c.bounds, ncb);
|
||||
Point o = p.layout.scroll_offset;
|
||||
p.children_bounds = containing_rect(cb + o, p.children_bounds);
|
||||
|
||||
// reset the children bounds
|
||||
c.children_bounds = {
|
||||
.x = c.bounds.x,
|
||||
.y = c.bounds.y
|
||||
};
|
||||
}
|
||||
|
||||
fn void? Ctx.layout_element_tree(&ctx)
|
||||
{
|
||||
int current;
|
||||
for (int n; (current = ctx.tree.level_order_it(0, n)) >= 0; n++) {
|
||||
Elem* p = ctx.find_elem(ctx.tree.get(current))!;
|
||||
p.layout.origin = -p.layout.scroll_offset;
|
||||
|
||||
int ch;
|
||||
// RESOLVE KNOWN DIMENSIONS
|
||||
for (int i; (ch = ctx.tree.children_it(current, i)) >= 0; i++) {
|
||||
Elem* c = ctx.find_elem(ctx.tree.get(ch))!;
|
||||
if (ctx.tree.is_root(ch)) {
|
||||
resolve_dimensions(p, &&{});
|
||||
} else {
|
||||
resolve_dimensions(c, p);
|
||||
}
|
||||
}
|
||||
|
||||
// RESOLVE GROW CHILDREN
|
||||
for (int i; (ch = ctx.tree.children_it(current, i)) >= 0; i++) {
|
||||
Elem* c = ctx.find_elem(ctx.tree.get(ch))!;
|
||||
if (ctx.tree.is_root(ch)) {
|
||||
resolve_grow_elements(p, &&{});
|
||||
} else {
|
||||
resolve_grow_elements(c, p);
|
||||
}
|
||||
}
|
||||
|
||||
// RESOLVE CHILDREN PLACEMENT
|
||||
for (int i; (ch = ctx.tree.children_it(current, i)) >= 0; i++) {
|
||||
Elem* c = ctx.find_elem(ctx.tree.get(ch))!;
|
||||
if (ctx.tree.is_root(ch)) {
|
||||
resolve_placement(p, &&{});
|
||||
} else {
|
||||
resolve_placement(c, p);
|
||||
}
|
||||
|
||||
// FIXME: this stuff would be better elsewhere but we are already iteraring through all
|
||||
// elements so here it fits really well
|
||||
ctx.update_hover_and_focus(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
367
src/mtree.c3
Normal file
367
src/mtree.c3
Normal file
@ -0,0 +1,367 @@
|
||||
module mtree{Type};
|
||||
|
||||
/* ================================================================================================
|
||||
* MTree, Bitmap-based tree
|
||||
* ================================================================================================
|
||||
*
|
||||
* Overview
|
||||
* --------
|
||||
* The MTree is a bitmap-based tree structure composed of three core elements:
|
||||
* - Element Vector: Stores user data.
|
||||
* - Reference Node Vector: Manages node relationships.
|
||||
* - Bitmap: Marks used indices.
|
||||
*
|
||||
* The name "MTree" originates from "Matrix Tree," where the vector is divided into
|
||||
* sectors of power-of-two sizes. Each node's bitmap marks the positions of its
|
||||
* children within the same sector.
|
||||
*
|
||||
* If a parent and its children are in different sectors, a new node is created.
|
||||
* The parent's "next" field points to this new node, forming a chain that must
|
||||
* be traversed during iteration.
|
||||
*
|
||||
*
|
||||
* Example (sector size = 8)
|
||||
* -------------------------
|
||||
*
|
||||
* _________________________________
|
||||
* |__ __ _______________________ |
|
||||
* | | | | _ |
|
||||
* | v v vv |v
|
||||
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
* refs_vec:| 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12|13|14|15|16|...
|
||||
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
* \__________ __________/ \__________ __________/ \__
|
||||
* V V
|
||||
* sector 0 sector 1
|
||||
*
|
||||
*
|
||||
* Node Relationships:
|
||||
* -------------------
|
||||
* - Root (Element 0) has three direct children: 1, 2, and 10.
|
||||
* - Node 10 is in a different sector than the root, so root.next points to Node 11.
|
||||
* - Node 11 has Node 10 as a direct child and Node 0 (root) as its parent.
|
||||
*
|
||||
* Bitmap Representation:
|
||||
* ---------------------
|
||||
*
|
||||
* root = {
|
||||
* .parent = -1; // Root has no parent
|
||||
* .next = 11; // Points to Node 11
|
||||
* .children = 0b00000110; // [0|1|1|0|0|0|0|0] (Children: 1, 2)
|
||||
* }
|
||||
*
|
||||
* node11 = {
|
||||
* .parent = 0; // Parent is root (Node 0)
|
||||
* .next = -1; // Last in the chain
|
||||
* .children = 0b00000100; // [0|0|1|0|0|0|0|0] (Child: 10)
|
||||
* }
|
||||
*
|
||||
* ================================================================================================
|
||||
*/
|
||||
|
||||
import std::core::mem;
|
||||
import std::core::mem::allocator;
|
||||
import std::io;
|
||||
import std::bits;
|
||||
import std::collections::list;
|
||||
|
||||
|
||||
alias Bitmap = ulong;
|
||||
const BITS = Bitmap.sizeof*8;
|
||||
|
||||
alias IdxList = List{int};
|
||||
|
||||
// next: if positive it contains the index of the next node that contains the children information
|
||||
struct RefNode {
|
||||
int next;
|
||||
int parent;
|
||||
Bitmap children;
|
||||
}
|
||||
|
||||
struct MTree {
|
||||
usz elements;
|
||||
Allocator allocator;
|
||||
IdxList queue;
|
||||
Bitmap[] used;
|
||||
Type[] elem_vec; // element vector
|
||||
RefNode[] refs_vec; // relationship vector
|
||||
}
|
||||
|
||||
|
||||
<* @param [&inout] tree *>
|
||||
fn void MTree.init(&tree, usz size, Allocator allocator = mem)
|
||||
{
|
||||
// round size to the nearest multiple of BITS
|
||||
size = size + size%BITS;
|
||||
|
||||
tree.elements = 0;
|
||||
tree.allocator = allocator;
|
||||
tree.queue.init(tree.allocator, size);
|
||||
tree.used = allocator::new_array(tree.allocator, Bitmap, size/BITS);
|
||||
tree.elem_vec = allocator::new_array(tree.allocator, Type, size);
|
||||
tree.refs_vec = allocator::new_array(tree.allocator, RefNode, size);
|
||||
|
||||
foreach (&r: tree.refs_vec) {
|
||||
r.next = -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
<* @param [&inout] tree *>
|
||||
fn void MTree.free(&tree)
|
||||
{
|
||||
tree.elements = 0;
|
||||
tree.queue.free();
|
||||
(void)allocator::free(tree.allocator, tree.used);
|
||||
(void)allocator::free(tree.allocator, tree.elem_vec);
|
||||
(void)allocator::free(tree.allocator, tree.refs_vec);
|
||||
}
|
||||
|
||||
|
||||
<* @param [&inout] tree *>
|
||||
fn int? MTree.get_free_spot(&tree)
|
||||
{
|
||||
foreach (idx, d: tree.used) {
|
||||
if (d != $typeof(d).max) {
|
||||
int spot = (int)idx*BITS + BITS-(int)d.clz();
|
||||
return spot;
|
||||
}
|
||||
}
|
||||
return CAPACITY_EXCEEDED?;
|
||||
}
|
||||
|
||||
<* @require idx >= 0 *>
|
||||
macro void MTree.set_used(&tree, int idx)
|
||||
{
|
||||
int r = idx % BITS;
|
||||
int q = idx / BITS;
|
||||
tree.used[q] |= (1l << r);
|
||||
}
|
||||
|
||||
<* @require idx >= 0 *>
|
||||
macro void MTree.unset_used(&tree, int idx)
|
||||
{
|
||||
int r = idx % BITS;
|
||||
int q = idx / BITS;
|
||||
tree.used[q] &= ~(1l << r);
|
||||
}
|
||||
|
||||
<* @require idx >= 0 *>
|
||||
macro bool MTree.is_used(&tree, int idx)
|
||||
{
|
||||
int r = idx % BITS;
|
||||
int q = idx / BITS;
|
||||
return !!(tree.used[q] & (1l << r));
|
||||
}
|
||||
|
||||
|
||||
// get the last node in the "next" chain
|
||||
<* @require tree.is_used(parent) == true *>
|
||||
fn int MTree.last_node(&tree, int parent)
|
||||
{
|
||||
while(tree.refs_vec[parent].next >= 0) {
|
||||
parent = tree.refs_vec[parent].next;
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@require tree.elements == 0 || tree.is_used(parent) == true
|
||||
@param [&inout] tree
|
||||
*>
|
||||
fn int? MTree.add(&tree, int parent, Type t)
|
||||
{
|
||||
int idx = tree.get_free_spot()!;
|
||||
int subtree = idx / BITS;
|
||||
|
||||
tree.set_used(idx);
|
||||
tree.elem_vec[idx] = t;
|
||||
tree.refs_vec[idx] = (RefNode){
|
||||
.parent = parent,
|
||||
.next = -1,
|
||||
};
|
||||
|
||||
tree.elements++;
|
||||
// root element, has no parent
|
||||
if (tree.elements == 1) {
|
||||
tree.refs_vec[idx].parent = -1;
|
||||
return idx;
|
||||
}
|
||||
|
||||
|
||||
// if the parent already has a node in the same subtree as the child then update that node's
|
||||
// children bitmap
|
||||
bool done;
|
||||
for (int p = parent; p >= 0; p = tree.refs_vec[p].next) {
|
||||
int ps = p/BITS;
|
||||
if (ps == subtree) {
|
||||
tree.refs_vec[p].children |= (1l << (idx%BITS));
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// on fail we need to create another parent node
|
||||
if (!done) {
|
||||
int new_next = tree.get_free_spot()!;
|
||||
// if the new node does not land in the same subtree as the child we cannot do
|
||||
// anything since the references are immutable
|
||||
if (new_next/BITS != subtree) {
|
||||
return CAPACITY_EXCEEDED?;
|
||||
}
|
||||
tree.set_used(new_next);
|
||||
tree.elements++;
|
||||
// update the "next" chain
|
||||
int last_link = tree.last_node(parent);
|
||||
tree.refs_vec[last_link].next = new_next;
|
||||
tree.refs_vec[new_next].next = -1;
|
||||
tree.refs_vec[new_next].children |= (long)(1 << (idx%BITS));
|
||||
tree.refs_vec[new_next].parent = last_link;
|
||||
// FIXME: the elem_vec is not updated, do we need to?
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
|
||||
// get the index of the n-th children of parent, -1 otherwise
|
||||
// usage: for (int i, c; (c = tree.children_it(parent, i)) >= 0; i++) { ... }
|
||||
<* @param [&in] tree *>
|
||||
fn int MTree.children_it(&tree, int parent, int n)
|
||||
{
|
||||
int tot_children;
|
||||
int child;
|
||||
for (int p = parent; p >= 0; p = tree.refs_vec[p].next) {
|
||||
int cn = (int)tree.refs_vec[p].children.popcount();
|
||||
tot_children += cn;
|
||||
|
||||
// we are in the right subtree
|
||||
if (tot_children > n) {
|
||||
child = (p/BITS) * BITS; // start at the parent's subtree index
|
||||
int j = cn - (tot_children - n); // we need the j-th children of this node
|
||||
Bitmap u = tree.refs_vec[p].children;
|
||||
|
||||
child += j; // add the children number
|
||||
do {
|
||||
child += (int)u.ctz(); // increment by the skipped zeroes
|
||||
u >>= u.ctz() + 1;
|
||||
j--;
|
||||
} while (j >= 0);
|
||||
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
<* @param [&in] tree *>
|
||||
fn int MTree.children_num(&tree, int parent)
|
||||
{
|
||||
int n;
|
||||
for (int p = parent; p >= 0; p = tree.refs_vec[p].next) {
|
||||
n += (int)tree.refs_vec[p].children.popcount();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
<* @param [&in] tree *>
|
||||
fn int MTree.subtree_size(&tree, int parent)
|
||||
{
|
||||
int x = tree.children_num(parent);
|
||||
int c;
|
||||
for (int n; (c = tree.children_it(parent, n)) >= 0; n++) {
|
||||
x += tree.subtree_size(c);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
<* @param [&inout] tree *>
|
||||
fn int MTree.level_order_it(&tree, int parent, int i)
|
||||
{
|
||||
if (i == 0) {
|
||||
tree.queue.clear();
|
||||
tree.queue.push(parent);
|
||||
}
|
||||
|
||||
if (tree.queue.len() == 0) return -1;
|
||||
|
||||
int p = tree.queue.pop_first()!!;
|
||||
int c;
|
||||
for (int n; (c = tree.children_it(p, n)) >= 0; n++) {
|
||||
tree.queue.push(c);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
<* @param [&inout] tree *>
|
||||
fn void MTree.prune(&tree, int parent)
|
||||
{
|
||||
if (!tree.is_used(parent)) return;
|
||||
|
||||
int c;
|
||||
for (int i = 0; (c = tree.children_it(parent, i)) >= 0; i++) {
|
||||
tree.prune(c); // prune the subtree
|
||||
|
||||
// delete all children including their next chain
|
||||
for (int p = c; p >= 0;) {
|
||||
int next = tree.refs_vec[p].next;
|
||||
tree.unset_used(p);
|
||||
tree.refs_vec[p] = {.next = -1};
|
||||
p = next;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// finally delete the parent
|
||||
for (int p = parent; p >= 0;) {
|
||||
int next = tree.refs_vec[p].next;
|
||||
tree.unset_used(p);
|
||||
tree.elements--;
|
||||
tree.refs_vec[p] = {.next = -1};
|
||||
p = next;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
<*
|
||||
@require ref >= 0 , ref < tree.elem_vec.len
|
||||
@param [&inout] tree
|
||||
*>
|
||||
fn Type? MTree.get(&tree, int ref) @operator([])
|
||||
{
|
||||
if (tree.is_used(ref)) return tree.elem_vec[ref];
|
||||
return NOT_FOUND?;
|
||||
}
|
||||
|
||||
<* @param [&in] tree *>
|
||||
fn Type? MTree.parentof(&tree, int ref)
|
||||
{
|
||||
if (!tree.is_used(ref)) return NOT_FOUND?;
|
||||
return tree.refs_vec[ref].parent;
|
||||
}
|
||||
|
||||
<* @param [&inout] tree *>
|
||||
fn void MTree.nuke(&tree)
|
||||
{
|
||||
foreach (idx, &b: tree.used) {
|
||||
*b = 0;
|
||||
tree.refs_vec[idx] = {.next = -1};
|
||||
}
|
||||
tree.elements = 0;
|
||||
}
|
||||
|
||||
<* @param [&in] t *>
|
||||
macro bool MTree.is_root(&t, int i) => t.is_used(i) && t.refs_vec[i].parent == -1;
|
||||
|
||||
<* @param [&in] tree *>
|
||||
fn void MTree.print(&tree)
|
||||
{
|
||||
foreach (idx, c: tree.elem_vec) {
|
||||
if (tree.is_used((int)idx)) {
|
||||
io::printfn("[%d](%s) parent:%d next:%d children:%b",
|
||||
idx, c, tree.refs_vec[idx].parent, tree.refs_vec[idx].next,
|
||||
tree.refs_vec[idx].children
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
268
src/shapes.c3
Normal file
268
src/shapes.c3
Normal file
@ -0,0 +1,268 @@
|
||||
module ugui;
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
// RECTANGLE //
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
|
||||
// Rect and it's methods
|
||||
struct Rect {
|
||||
short x, y, w, h;
|
||||
}
|
||||
|
||||
// TODO: find another name
|
||||
const Rect RECT_MAX = {0, 0, short.max, short.max};
|
||||
|
||||
// return true if rect a contains b
|
||||
macro bool Rect.contains(Rect a, Rect b)
|
||||
{
|
||||
return (a.x <= b.x && a.y <= b.y && a.x+a.w >= b.x+b.w && a.y+a.h >= b.y+b.h);
|
||||
}
|
||||
|
||||
// returns the intersection of a and b
|
||||
macro Rect Rect.intersection(Rect a, Rect b)
|
||||
{
|
||||
return {
|
||||
.x = (short)max(a.x, b.x),
|
||||
.y = (short)max(a.y, b.y),
|
||||
.w = (short)min(a.x+a.w, b.x+b.w) - (short)max(a.x, b.x),
|
||||
.h = (short)min(a.y+a.h, b.y+b.h) - (short)max(a.y, b.y),
|
||||
};
|
||||
}
|
||||
|
||||
// returns true if the intersection not null
|
||||
macro bool Rect.collides(Rect a, Rect b)
|
||||
{
|
||||
return !(a.x > b.x+b.w || a.x+a.w < b.x || a.y > b.y+b.h || a.y+a.h < b.y);
|
||||
}
|
||||
|
||||
// return a rect that contains both rects, a bounding box of both
|
||||
macro Rect containing_rect(Rect a, Rect b)
|
||||
{
|
||||
short min_x = (short)min(a.x, b.x);
|
||||
short min_y = (short)min(a.y, b.y);
|
||||
short max_x = (short)max(a.x + a.w, b.x + b.w);
|
||||
short max_y = (short)max(a.y + a.h, b.y + b.h);
|
||||
return {
|
||||
.x = min_x,
|
||||
.y = min_y,
|
||||
.w = (short)(max_x - min_x),
|
||||
.h = (short)(max_y - min_y)
|
||||
};
|
||||
}
|
||||
|
||||
// check for empty rect
|
||||
macro bool Rect.is_null(Rect r) => r.x == 0 && r.y == 0 && r.x == 0 && r.w == 0;
|
||||
|
||||
// returns the element-wise addition of r1 and r2
|
||||
macro Rect Rect.add(Rect r1, Rect r2) @operator_s(+)
|
||||
{
|
||||
return {
|
||||
.x = r1.x + r2.x,
|
||||
.y = r1.y + r2.y,
|
||||
.w = r1.w + r2.w,
|
||||
.h = r1.h + r2.h,
|
||||
};
|
||||
}
|
||||
|
||||
// returns the element-wise subtraction of r1 and r2
|
||||
macro Rect Rect.sub(Rect r1, Rect r2) @operator_s(-)
|
||||
{
|
||||
return {
|
||||
.x = r1.x - r2.x,
|
||||
.y = r1.y - r2.y,
|
||||
.w = r1.w - r2.w,
|
||||
.h = r1.h - r2.h,
|
||||
};
|
||||
}
|
||||
|
||||
// returns the element-wise multiplication of r1 and r2
|
||||
macro Rect Rect.mul(Rect r1, Rect r2) @operator_s(*)
|
||||
{
|
||||
return {
|
||||
.x = r1.x * r2.x,
|
||||
.y = r1.y * r2.y,
|
||||
.w = r1.w * r2.w,
|
||||
.h = r1.h * r2.h,
|
||||
};
|
||||
}
|
||||
|
||||
macro Point Rect.position(Rect r)
|
||||
{
|
||||
return {
|
||||
.x = r.x,
|
||||
.y = r.y,
|
||||
};
|
||||
}
|
||||
|
||||
macro Point Rect.size(Rect r)
|
||||
{
|
||||
return {
|
||||
.x = r.w,
|
||||
.y = r.h,
|
||||
};
|
||||
}
|
||||
|
||||
macro Rect Rect.max(Rect a, Rect b)
|
||||
{
|
||||
return {
|
||||
.x = max(a.x, b.x),
|
||||
.y = max(a.y, b.y),
|
||||
.w = max(a.w, b.w),
|
||||
.h = max(a.h, b.h),
|
||||
};
|
||||
}
|
||||
|
||||
macro Rect Rect.min(Rect a, Rect b)
|
||||
{
|
||||
return {
|
||||
.x = min(a.x, b.x),
|
||||
.y = min(a.y, b.y),
|
||||
.w = min(a.w, b.w),
|
||||
.h = min(a.h, b.h),
|
||||
};
|
||||
}
|
||||
|
||||
// Offset a rect by a point
|
||||
macro Rect Rect.off(Rect r, Point p) @operator_s(+)
|
||||
{
|
||||
return {
|
||||
.x = r.x + p.x,
|
||||
.y = r.y + p.y,
|
||||
.w = r.w,
|
||||
.h = r.h,
|
||||
};
|
||||
}
|
||||
|
||||
// Resize a rect width and height
|
||||
macro Rect Rect.grow(Rect r, Point p)
|
||||
{
|
||||
return {
|
||||
.x = r.x,
|
||||
.y = r.y,
|
||||
.w = r.w + p.x,
|
||||
.h = r.h + p.y,
|
||||
};
|
||||
}
|
||||
|
||||
// Return the bottom-right corner of a rectangle
|
||||
macro Point Rect.bottom_right(Rect r)
|
||||
{
|
||||
return {
|
||||
.x = r.x + r.w,
|
||||
.y = r.y + r.h,
|
||||
};
|
||||
}
|
||||
|
||||
macro Rect Rect.center_to(Rect a, Rect b)
|
||||
{
|
||||
return {
|
||||
.x = b.x + (b.w - a.w)/2,
|
||||
.y = b.y + (b.h - a.h)/2,
|
||||
.w = a.w,
|
||||
.h = a.h,
|
||||
};
|
||||
}
|
||||
|
||||
macro Rect Rect.pad(Rect a, Rect b)
|
||||
{
|
||||
return {
|
||||
.x = a.x + b.x,
|
||||
.y = a.y + b.y,
|
||||
.w = a.w - b.x - b.w,
|
||||
.h = a.h - b.y - b.h,
|
||||
};
|
||||
}
|
||||
|
||||
macro Rect Rect.expand(Rect a, Rect b)
|
||||
{
|
||||
return {
|
||||
.x = a.x - b.x,
|
||||
.y = a.y - b.y,
|
||||
.w = a.w + b.x + b.w,
|
||||
.h = a.h + b.y + b.h,
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
// POINT //
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
|
||||
struct Point {
|
||||
short x, y;
|
||||
}
|
||||
|
||||
// returns true if a point is inside the rectangle
|
||||
macro bool Point.in_rect(Point p, Rect r)
|
||||
{
|
||||
return (p.x >= r.x && p.x <= r.x + r.w) && (p.y >= r.y && p.y <= r.y + r.h);
|
||||
}
|
||||
|
||||
macro bool Point.outside(Point p, Rect r) => !p.in_rect(r);
|
||||
|
||||
macro Point Point.add(Point a, Point b) @operator_s(+) => {.x = a.x+b.x, .y = a.y+b.y};
|
||||
macro Point Point.sub(Point a, Point b) @operator_s(-) => {.x = a.x-b.x, .y = a.y-b.y};
|
||||
macro Point Point.neg(Point p) @operator_s(-) => {-p.x, -p.y};
|
||||
|
||||
macro Point Point.max(Point a, Point b) => {.x = max(a.x, b.x), .y = max(a.y, b.y)};
|
||||
macro Point Point.min(Point a, Point b) => {.x = min(a.x, b.x), .y = min(a.y, b.y)};
|
||||
|
||||
macro bool Point.equals(Point a, Point b) @operator_s(==) => a.x == b.x && a.y == b.y;
|
||||
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
// COLOR //
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
|
||||
struct Color{
|
||||
char r, g, b, a;
|
||||
}
|
||||
|
||||
macro Color uint.to_rgba(u)
|
||||
{
|
||||
return {
|
||||
.r = (char)((u >> 24) & 0xff),
|
||||
.g = (char)((u >> 16) & 0xff),
|
||||
.b = (char)((u >> 8) & 0xff),
|
||||
.a = (char)((u >> 0) & 0xff)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
macro Color uint.@to_rgba($u)
|
||||
{
|
||||
return {
|
||||
.r = (char)(($u >> 24) & 0xff),
|
||||
.g = (char)(($u >> 16) & 0xff),
|
||||
.b = (char)(($u >> 8) & 0xff),
|
||||
.a = (char)(($u >> 0) & 0xff)
|
||||
};
|
||||
}
|
||||
|
||||
macro uint Color.to_uint(c) => c.r | (c.g << 8) | (c.b << 16) | (c.a << 24);
|
||||
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
// SIZE //
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
|
||||
macro short short.add_no_of(short a, short b) => (short)max(min((int)a + (int)b, short.max), short.min) @inline;
|
||||
|
||||
struct Size {
|
||||
short min, max;
|
||||
}
|
||||
|
||||
macro Size @grow() => {.min = 0, .max = 0};
|
||||
macro Size @exact(short s) => {.min = s, .max = s};
|
||||
macro Size @fit(short min = 0, short max = short.max) => {.min = min, .max = max};
|
||||
|
||||
macro bool Size.@is_grow(s) => (s.min == 0 && s.max == 0);
|
||||
macro bool Size.@is_exact(s) => (s.min == s.max && s.min != 0);
|
||||
macro bool Size.@is_fit(s) => (s.min != s.max);
|
||||
|
||||
macro Size Size.add(a, Size b) @operator_s(+) => {.min = a.min.add_no_of(b.min), .max = a.max.add_no_of(b.max)};
|
||||
macro Size Size.sub(a, Size b) @operator_s(-) => {.min = a.min.add_no_of(-b.min), .max = a.max.add_no_of(-b.max)};
|
||||
|
||||
macro Size Size.combine(a, Size b) => {.min = max(a.min, b.min), .max = min(a.max, b.max)};
|
||||
macro Size Size.comb_max(a, Size b) => {.min = max(a.min, b.min), .max = max(a.max, b.max)};
|
||||
macro Size Size.comb_min(a, Size b) => {.min = min(a.min, b.min), .max = min(a.max, b.max)};
|
||||
|
||||
macro short Size.greater(a) => a.min > a.max ? a.min : a.max;
|
||||
107
src/sprite.c3
Normal file
107
src/sprite.c3
Normal file
@ -0,0 +1,107 @@
|
||||
module ugui;
|
||||
|
||||
import std::core::mem::allocator;
|
||||
import std::collections::map;
|
||||
import std::io;
|
||||
import std::compression::qoi;
|
||||
|
||||
const usz SRITES_PER_ATLAS = 64;
|
||||
|
||||
enum SpriteType {
|
||||
SPRITE_NORMAL,
|
||||
SPRITE_SDF,
|
||||
SPRITE_MSDF,
|
||||
SPRITE_ANIMATED,
|
||||
}
|
||||
|
||||
struct Sprite {
|
||||
Id id;
|
||||
SpriteType type;
|
||||
ushort u, v;
|
||||
ushort w, h;
|
||||
}
|
||||
|
||||
alias SpriteMap = map::HashMap{Id, Sprite};
|
||||
|
||||
struct SpriteAtlas {
|
||||
Id id;
|
||||
Atlas atlas;
|
||||
SpriteMap sprites;
|
||||
bool should_update;
|
||||
}
|
||||
|
||||
// name: some examples are "icons" or "images"
|
||||
fn void? SpriteAtlas.init(&this, String name, AtlasType type, ushort width, ushort height)
|
||||
{
|
||||
// FIXME: for now only R8G8B8A8 format is supported
|
||||
if (type != ATLAS_R8G8B8A8) {
|
||||
return INVALID_TYPE?;
|
||||
}
|
||||
|
||||
this.id = name.hash();
|
||||
this.atlas.new(this.id, AtlasType.ATLAS_R8G8B8A8, width, height)!;
|
||||
this.sprites.init(allocator::mem, capacity: SRITES_PER_ATLAS);
|
||||
this.should_update = false;
|
||||
}
|
||||
|
||||
fn void? SpriteAtlas.free(&this)
|
||||
{
|
||||
this.atlas.free();
|
||||
this.sprites.free();
|
||||
}
|
||||
|
||||
// FIXME: this should throw an error when a different pixel format than the atlas' is used
|
||||
// or convert from the source's pixel format to the atlas'
|
||||
fn Sprite*? SpriteAtlas.insert(&this, String name, SpriteType type, char[] pixels, ushort w, ushort h, ushort stride)
|
||||
{
|
||||
Sprite s;
|
||||
s.id = name.hash();
|
||||
s.type = type;
|
||||
Point uv = this.atlas.place(pixels, w, h, stride)!;
|
||||
s.w = w;
|
||||
s.h = h;
|
||||
s.u = uv.x;
|
||||
s.v = uv.y;
|
||||
this.sprites.set(s.id, s);
|
||||
this.should_update = true;
|
||||
return this.sprites.get_ref(s.id);
|
||||
}
|
||||
|
||||
fn Sprite*? SpriteAtlas.get(&this, String name)
|
||||
{
|
||||
Id id = name.hash();
|
||||
return this.sprites.get_ref(id);
|
||||
}
|
||||
|
||||
fn Sprite*? SpriteAtlas.get_by_id(&this, Id id)
|
||||
{
|
||||
return this.sprites.get_ref(id);
|
||||
}
|
||||
|
||||
macro Rect Sprite.rect(s) => {0,0,s.w,s.h};
|
||||
macro Rect Sprite.uv(s) => {s.u,s.v,s.w,s.h};
|
||||
|
||||
fn void? Ctx.sprite_atlas_create(&ctx, String name, AtlasType type, ushort w, ushort h)
|
||||
{
|
||||
ctx.sprite_atlas.init(name, type, w, h)!;
|
||||
}
|
||||
|
||||
fn Id Ctx.get_sprite_atlas_id(&ctx, String name)
|
||||
{
|
||||
return name.hash();
|
||||
}
|
||||
|
||||
fn void? Ctx.import_sprite_memory(&ctx, String name, char[] pixels, ushort w, ushort h, ushort stride, SpriteType type = SPRITE_NORMAL)
|
||||
{
|
||||
ctx.sprite_atlas.insert(name, type, pixels, w, h, stride)!;
|
||||
}
|
||||
|
||||
fn void? Ctx.import_sprite_file_qoi(&ctx, String name, String path, SpriteType type = SPRITE_NORMAL)
|
||||
{
|
||||
QOIDesc desc;
|
||||
|
||||
char[] pixels = qoi::read(allocator::mem, path, &desc, QOIChannels.RGBA)!;
|
||||
defer mem::free(pixels);
|
||||
|
||||
ctx.sprite_atlas.insert(name, type, pixels, (ushort)desc.width, (ushort)desc.height, (ushort)desc.width)!;
|
||||
}
|
||||
588
src/string.c3
Normal file
588
src/string.c3
Normal file
@ -0,0 +1,588 @@
|
||||
module ugui;
|
||||
|
||||
import std::collections::list;
|
||||
import std::core::string;
|
||||
|
||||
struct LineInfo @local {
|
||||
usz start, end;
|
||||
short width, height;
|
||||
short first_off; // first character offset
|
||||
}
|
||||
|
||||
macro usz LineInfo.len(li) => li.end-li.start;
|
||||
|
||||
alias LineStack @local = list::List{LineInfo};
|
||||
|
||||
fn short Rect.y_off(Rect bounds, short height, Anchor anchor) @local
|
||||
{
|
||||
short off;
|
||||
|
||||
switch (anchor) {
|
||||
case TOP_LEFT: nextcase;
|
||||
case TOP: nextcase;
|
||||
case TOP_RIGHT:
|
||||
off = 0;
|
||||
case LEFT: nextcase;
|
||||
case CENTER: nextcase;
|
||||
case RIGHT:
|
||||
off = (short)(bounds.h - height)/2;
|
||||
case BOTTOM_LEFT: nextcase;
|
||||
case BOTTOM: nextcase;
|
||||
case BOTTOM_RIGHT:
|
||||
off = (short)(bounds.h - height);
|
||||
}
|
||||
return off;
|
||||
}
|
||||
|
||||
fn short Rect.x_off(Rect bounds, short width, Anchor anchor) @local
|
||||
{
|
||||
short off;
|
||||
|
||||
switch (anchor) {
|
||||
case TOP_LEFT: nextcase;
|
||||
case LEFT: nextcase;
|
||||
case BOTTOM_LEFT:
|
||||
off = 0;
|
||||
case TOP: nextcase;
|
||||
case CENTER: nextcase;
|
||||
case BOTTOM:
|
||||
off = (short)(bounds.w - width)/2;
|
||||
case TOP_RIGHT: nextcase;
|
||||
case RIGHT: nextcase;
|
||||
case BOTTOM_RIGHT:
|
||||
off = (short)(bounds.w - width);
|
||||
}
|
||||
|
||||
return off;
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
// STRING LAYOUT //
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
|
||||
|
||||
struct GlyphIterator {
|
||||
short baseline;
|
||||
short line_height;
|
||||
short line_gap;
|
||||
short space_width;
|
||||
short tab_width;
|
||||
Rect bounds;
|
||||
Anchor anchor;
|
||||
bool reflow;
|
||||
Font* font;
|
||||
|
||||
LineStack lines;
|
||||
usz line_off, line_idx;
|
||||
String text;
|
||||
|
||||
Codepoint cp;
|
||||
Glyph* gp;
|
||||
short adv; // prev advance
|
||||
Point o;
|
||||
Rect str_bounds;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] self
|
||||
@param [in] text
|
||||
@param [&inout] font
|
||||
*>
|
||||
fn void? GlyphIterator.init(&self, Allocator allocator, String text, Rect bounds, Font* font, Anchor anchor, bool reflow, uint tab_size)
|
||||
{
|
||||
self.font = font;
|
||||
|
||||
self.line_height = (short)font.line_height();
|
||||
self.baseline = (short)font.ascender;
|
||||
self.line_gap = (short)font.linegap;
|
||||
self.space_width = font.get_glyph(' ').adv!;
|
||||
self.tab_width = self.space_width * (short)tab_size;
|
||||
|
||||
self.bounds = bounds;
|
||||
self.o = bounds.position();
|
||||
self.reflow = reflow;
|
||||
self.text = text;
|
||||
self.anchor = anchor;
|
||||
|
||||
// if the anchor is top_left we can skip dividing the string line by line, in GlyphIterator.next
|
||||
// this has to be accounted for
|
||||
if (anchor != TOP_LEFT) {
|
||||
self.lines.init(allocator, 4);
|
||||
self.populate_lines_stack()!;
|
||||
self.line_off = 0;
|
||||
self.line_idx = 0;
|
||||
|
||||
if (self.lines.len() > 0) {
|
||||
self.o.y += bounds.y_off(self.str_bounds.h, anchor);
|
||||
self.o.x += bounds.x_off(self.lines[0].width, anchor) - self.lines[0].first_off;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn void? GlyphIterator.populate_lines_stack(&self)
|
||||
{
|
||||
usz line_start;
|
||||
LineInfo li;
|
||||
Point o = self.o;
|
||||
StringIterator ti = self.text.iterator();
|
||||
|
||||
usz prev_off;
|
||||
for (Codepoint cp; ti.has_next();) {
|
||||
cp = ti.next()!;
|
||||
usz off = ti.current;
|
||||
bool push = false;
|
||||
|
||||
li.height = self.line_height;
|
||||
switch {
|
||||
case cp == '\n':
|
||||
push = true;
|
||||
case cp == '\t':
|
||||
o.x += self.tab_width;
|
||||
case ascii::is_cntrl((char)cp):
|
||||
break;
|
||||
default:
|
||||
Glyph* gp = self.font.get_glyph(cp)!;
|
||||
|
||||
if (off == line_start) {
|
||||
li.first_off = gp.ox;
|
||||
o.x -= gp.ox;
|
||||
}
|
||||
|
||||
Rect b = gp.bounds().off(o);
|
||||
b.y += self.baseline;
|
||||
|
||||
if (self.reflow && b.x + b.w > self.bounds.x + self.bounds.w) {
|
||||
push = true;
|
||||
// roll back this character since it is on the next line
|
||||
ti.current = prev_off;
|
||||
off = prev_off;
|
||||
} else {
|
||||
o.x += gp.adv;
|
||||
li.width += gp.adv;
|
||||
}
|
||||
}
|
||||
|
||||
if (push) {
|
||||
li.start = line_start;
|
||||
li.end = off;
|
||||
self.lines.push(li);
|
||||
self.str_bounds.w = max(self.str_bounds.w, li.width);
|
||||
self.str_bounds.h += li.height;
|
||||
|
||||
o.x = self.bounds.x;
|
||||
o.y += self.line_height;
|
||||
line_start = off;
|
||||
|
||||
li.height = 0;
|
||||
li.width = 0;
|
||||
}
|
||||
|
||||
prev_off = off;
|
||||
}
|
||||
if (line_start != ti.current) {
|
||||
// FIXME: crap, can we not repeat this code?
|
||||
li.start = line_start;
|
||||
li.end = ti.current;
|
||||
self.lines.push(li);
|
||||
self.str_bounds.w = max(self.str_bounds.w, li.width);
|
||||
self.str_bounds.h += li.height;
|
||||
}
|
||||
|
||||
self.str_bounds.h += (short)(self.lines.len()-1) * self.line_gap;
|
||||
}
|
||||
|
||||
fn String GlyphIterator.current_line(&self)
|
||||
{
|
||||
LineInfo li = self.lines[self.line_idx];
|
||||
return self.text[li.start:li.len()];
|
||||
}
|
||||
|
||||
|
||||
fn Rect? GlyphIterator.next(&self)
|
||||
{
|
||||
// check if there is a next glyph and maybe update the line and offset indices
|
||||
if (self.anchor != TOP_LEFT) {
|
||||
if (self.line_idx >= self.lines.len()) {
|
||||
return NO_MORE_ELEMENT?;
|
||||
}
|
||||
|
||||
LineInfo li = self.lines[self.line_idx];
|
||||
if (self.line_off >= li.len()) {
|
||||
self.line_idx++;
|
||||
|
||||
if (self.line_idx >= self.lines.len()) {
|
||||
return NO_MORE_ELEMENT?;
|
||||
}
|
||||
|
||||
self.line_off = 0;
|
||||
li = self.lines[self.line_idx];
|
||||
|
||||
self.o.y += self.line_height + self.line_gap;
|
||||
self.o.x = self.bounds.x + self.bounds.x_off(li.width, self.anchor) - li.first_off;
|
||||
}
|
||||
} else if (self.line_off >= self.text.len) {
|
||||
return NO_MORE_ELEMENT?;
|
||||
}
|
||||
|
||||
String t;
|
||||
if (self.anchor != TOP_LEFT) {
|
||||
t = self.current_line()[self.line_off..];
|
||||
} else {
|
||||
t = self.text[self.line_off..];
|
||||
}
|
||||
|
||||
usz read = t.len < 4 ? t.len : 4;
|
||||
self.cp = conv::utf8_to_char32(&t[0], &read)!;
|
||||
self.line_off += read;
|
||||
self.gp = self.font.get_glyph(self.cp)!;
|
||||
|
||||
Rect b = {.x = self.o.x, .y = self.o.y};
|
||||
|
||||
Point prev_o = self.o;
|
||||
self.adv = 0;
|
||||
switch {
|
||||
case self.cp == '\n':
|
||||
if (self.anchor == TOP_LEFT) {
|
||||
self.o.x = self.bounds.x;
|
||||
self.o.y += self.line_height + self.line_gap;
|
||||
self.line_idx++;
|
||||
}
|
||||
break;
|
||||
case self.cp == '\t':
|
||||
self.o.x += self.tab_width;
|
||||
case ascii::is_cntrl((char)self.cp):
|
||||
break;
|
||||
default:
|
||||
b = self.gp.bounds().off(self.o);
|
||||
b.y += self.baseline;
|
||||
if (self.anchor == TOP_LEFT) {
|
||||
//if (self.o.x == self.bounds.x) self.bounds.x -= self.gp.ox;
|
||||
if (self.reflow && b.bottom_right().x > self.bounds.bottom_right().x) {
|
||||
self.o.x = self.bounds.x - self.gp.ox;
|
||||
self.o.y += self.line_height + self.line_gap;
|
||||
self.line_idx++;
|
||||
b = self.gp.bounds().off(self.o);
|
||||
b.y += self.baseline;
|
||||
}
|
||||
}
|
||||
self.o.x += self.gp.adv;
|
||||
self.adv = self.o.x - prev_o.x;
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
fn bool GlyphIterator.has_next(&self)
|
||||
{
|
||||
if (self.anchor == TOP_LEFT) {
|
||||
return self.line_off < self.text.len;
|
||||
}
|
||||
|
||||
if (self.line_idx >= self.lines.len()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LineInfo li = self.lines[self.line_idx];
|
||||
if (self.line_idx == self.lines.len() - 1 && self.line_off >= li.len()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn usz GlyphIterator.current_offset(&self)
|
||||
{
|
||||
if (self.anchor == TOP_LEFT) return self.line_off;
|
||||
return self.lines[self.line_idx].start + self.line_off;
|
||||
}
|
||||
|
||||
|
||||
// layout a string inside a bounding box, following the given alignment (anchor).
|
||||
<*
|
||||
@param [&in] ctx
|
||||
@param [in] text
|
||||
*>
|
||||
fn void? Ctx.layout_string(&ctx, String text, Rect bounds, Anchor anchor, int z_index, Color hue, bool reflow = false)
|
||||
{
|
||||
if (text == "") return;
|
||||
if (bounds.w <= 0 || bounds.h <= 0) return;
|
||||
ctx.push_scissor(bounds, z_index)!;
|
||||
|
||||
Font* font = &ctx.font;
|
||||
Id texture_id = font.id;
|
||||
|
||||
GlyphIterator gi;
|
||||
gi.init(tmem, text, bounds, font, anchor, reflow, TAB_SIZE)!;
|
||||
while (gi.has_next()) {
|
||||
Rect b = gi.next()!;
|
||||
Rect uv = gi.gp.uv();
|
||||
ctx.push_sprite(b, uv, texture_id, z_index, hue)!;
|
||||
}
|
||||
|
||||
ctx.reset_scissor(z_index)!;
|
||||
// ctx.dbg_rect(str_bounds.off(bounds.position()));
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
// CURSOR AND MOUSE //
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
|
||||
|
||||
fn Rect? Ctx.get_cursor_position(&ctx, String text, Rect bounds, Anchor anchor, usz cursor, bool reflow = false)
|
||||
{
|
||||
if (bounds.w <= 0 || bounds.h <= 0) return {};
|
||||
|
||||
Font* font = &ctx.font;
|
||||
Id texture_id = font.id;
|
||||
|
||||
if (text == "") text = "\f";
|
||||
|
||||
GlyphIterator gi;
|
||||
gi.init(tmem, text, bounds, font, anchor, reflow, TAB_SIZE)!;
|
||||
Rect cursor_rect;
|
||||
cursor_rect.x = gi.o.x;
|
||||
cursor_rect.y = gi.o.y;
|
||||
cursor_rect.h = (short)font.line_height();
|
||||
if (cursor == 0) return cursor_rect;
|
||||
|
||||
while (gi.has_next()) {
|
||||
Rect b = gi.next()!;
|
||||
if (gi.current_offset() == cursor) {
|
||||
if (gi.cp == '\n') {
|
||||
if (!gi.has_next()) {
|
||||
cursor_rect.x = bounds.x + bounds.x_off(0, anchor);
|
||||
cursor_rect.y = b.y + gi.line_height + gi.line_gap;
|
||||
} else {
|
||||
gi.next()!;
|
||||
cursor_rect.x = gi.o.x - gi.gp.adv;
|
||||
cursor_rect.y = gi.o.y;
|
||||
}
|
||||
} else {
|
||||
// Use the updated origin position instead of glyph bounds
|
||||
cursor_rect.x = gi.o.x;
|
||||
cursor_rect.y = gi.o.y;
|
||||
}
|
||||
return cursor_rect;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&in] ctx
|
||||
@param [in] text
|
||||
*>
|
||||
fn usz? Ctx.hit_test_string(&ctx, String text, Rect bounds, Anchor anchor, Point p, bool reflow = false)
|
||||
{
|
||||
if (text == "") return 0;
|
||||
if (bounds.w <= 0 || bounds.h <= 0) return 0;
|
||||
|
||||
Font* font = &ctx.font;
|
||||
|
||||
GlyphIterator gi;
|
||||
gi.init(tmem, text, bounds, font, anchor, reflow, TAB_SIZE)!;
|
||||
|
||||
usz prev_offset = 0;
|
||||
Point prev_o = gi.o;
|
||||
|
||||
while (gi.has_next()) {
|
||||
Point o_before = gi.o;
|
||||
usz offset_before = gi.current_offset();
|
||||
Rect b = gi.next()!;
|
||||
|
||||
switch {
|
||||
case gi.cp == '\n':
|
||||
// Check if point is on this line before the newline
|
||||
Rect line_rect = {
|
||||
.x = prev_o.x,
|
||||
.y = prev_o.y,
|
||||
.w = (short)(o_before.x - prev_o.x),
|
||||
.h = gi.line_height
|
||||
};
|
||||
if (p.in_rect(line_rect)) return offset_before;
|
||||
prev_o = gi.o;
|
||||
break;
|
||||
case gi.cp == '\t':
|
||||
// Check if point is in the tab space
|
||||
Rect tab_rect = {
|
||||
.x = o_before.x,
|
||||
.y = o_before.y,
|
||||
.w = gi.tab_width,
|
||||
.h = gi.line_height
|
||||
};
|
||||
if (p.in_rect(tab_rect)) return offset_before;
|
||||
break;
|
||||
case ascii::is_cntrl((char)gi.cp):
|
||||
break;
|
||||
default:
|
||||
// Create a hit test rect for this character
|
||||
Rect hit_rect = {
|
||||
.x = o_before.x,
|
||||
.y = o_before.y,
|
||||
.w = gi.gp.adv,
|
||||
.h = gi.line_height
|
||||
};
|
||||
|
||||
if (p.in_rect(hit_rect)) {
|
||||
// Check if cursor should be before or after this character
|
||||
// by checking which half of the character was clicked
|
||||
short mid_x = o_before.x + gi.gp.adv / 2;
|
||||
if (p.x < mid_x) {
|
||||
return offset_before;
|
||||
} else {
|
||||
return gi.current_offset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prev_offset = gi.current_offset();
|
||||
}
|
||||
|
||||
// Point is after all text
|
||||
return text.len;
|
||||
}
|
||||
|
||||
// TODO: implement a function `layout_string_with_selection` to avoid iterating over the string twice
|
||||
fn void? Ctx.draw_string_selection(&ctx, String text, Rect bounds, Anchor anchor, usz start, usz end, int z_index, Color hue, bool reflow = false)
|
||||
{
|
||||
if (text == "") return;
|
||||
if (bounds.w <= 0 || bounds.h <= 0) return;
|
||||
|
||||
if (start > end) @swap(start, end);
|
||||
|
||||
// Ensure start < end
|
||||
if (start > end) {
|
||||
usz temp = start;
|
||||
start = end;
|
||||
end = temp;
|
||||
}
|
||||
|
||||
ctx.push_scissor(bounds, z_index)!;
|
||||
|
||||
Font* font = &ctx.font;
|
||||
|
||||
GlyphIterator gi;
|
||||
gi.init(tmem, text, bounds, font, anchor, reflow, TAB_SIZE)!;
|
||||
|
||||
Rect sel_rect = { .h = gi.line_height }; // selection rect
|
||||
isz sel_line = -1; // selection line
|
||||
while (gi.has_next()) {
|
||||
Rect b = gi.next()!;
|
||||
usz off = gi.current_offset()-1;
|
||||
isz line = gi.line_idx;
|
||||
bool in_selection = start <= off && off <= end;
|
||||
|
||||
if (in_selection && line != sel_line) {
|
||||
if (sel_line != -1) {
|
||||
ctx.push_rect(sel_rect, z_index, &&{.bg = hue})!;
|
||||
}
|
||||
sel_rect = {.x = gi.o.x - gi.adv, .y = gi.o.y, .w = 0, .h = gi.line_height};
|
||||
sel_line = line;
|
||||
}
|
||||
if (in_selection) {
|
||||
sel_rect.w = gi.o.x - sel_rect.x;
|
||||
if (gi.cp == '\n') sel_rect.w += gi.space_width;
|
||||
}
|
||||
|
||||
if (off > end) break;
|
||||
}
|
||||
ctx.push_rect(sel_rect, z_index, &&{.bg = hue})!;
|
||||
|
||||
ctx.reset_scissor(z_index)!;
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
// TEXT MEASUREMENT //
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
|
||||
|
||||
const uint TAB_SIZE = 4;
|
||||
|
||||
struct TextSize {
|
||||
Size width, height;
|
||||
int area;
|
||||
}
|
||||
|
||||
// Measeure the size of a string.
|
||||
// width.min: as if each word is broken up by a new line
|
||||
// width.max: the width of the string left as-is
|
||||
// height.min: the height of the string left as-is
|
||||
// height.max: the height of the string with each word broken up by a new line
|
||||
<*
|
||||
@param [&in] ctx
|
||||
@param [in] text
|
||||
*>
|
||||
fn TextSize? Ctx.measure_string(&ctx, String text)
|
||||
{
|
||||
if (text == "") return (TextSize){};
|
||||
|
||||
Font* font = &ctx.font;
|
||||
short baseline = (short)font.ascender;
|
||||
short line_height = (short)font.line_height();
|
||||
short line_gap = (short)font.linegap;
|
||||
short space_width = font.get_glyph(' ').adv!;
|
||||
short tab_width = space_width * TAB_SIZE;
|
||||
|
||||
isz off;
|
||||
usz x;
|
||||
|
||||
TextSize ts;
|
||||
|
||||
short word_width;
|
||||
short words = 1;
|
||||
Rect bounds; // unaltered text bounds;
|
||||
Point origin;
|
||||
|
||||
StringIterator it = text.iterator();
|
||||
for (Codepoint cp; it.has_next();) {
|
||||
cp = it.next()!;
|
||||
Glyph* gp = font.get_glyph(cp)!;
|
||||
|
||||
// update the text bounds
|
||||
switch {
|
||||
case cp == '\n':
|
||||
origin.x = 0;
|
||||
origin.y += line_height + line_gap;
|
||||
case cp == '\t':
|
||||
origin.x += tab_width;
|
||||
case ascii::is_cntrl((char)cp):
|
||||
break;
|
||||
default:
|
||||
Rect b = gp.bounds().off(origin);
|
||||
b.y += baseline;
|
||||
bounds = containing_rect(bounds, b);
|
||||
origin.x += gp.adv;
|
||||
}
|
||||
|
||||
// update the word width
|
||||
switch {
|
||||
case ascii::is_space((char)cp):
|
||||
if (word_width > ts.width.min) ts.width.min = word_width;
|
||||
word_width = 0;
|
||||
words++;
|
||||
default:
|
||||
//word_width += gp.w + gp.ox;
|
||||
if (off < text.len) {
|
||||
word_width += gp.adv;
|
||||
} else {
|
||||
word_width += gp.w + gp.ox;
|
||||
}
|
||||
}
|
||||
}
|
||||
// end of string is also end of word
|
||||
if (word_width > ts.width.min) ts.width.min = word_width;
|
||||
|
||||
ts.width.max = bounds.w;
|
||||
ts.height.min = bounds.h;
|
||||
ts.height.max = words * line_height + line_gap * (words-1);
|
||||
ts.area = bounds.w * bounds.h;
|
||||
|
||||
return ts;
|
||||
}
|
||||
|
||||
|
||||
461
src/style.c3
Normal file
461
src/style.c3
Normal file
@ -0,0 +1,461 @@
|
||||
module ugui;
|
||||
|
||||
import std::collections::map;
|
||||
import std::core::mem::allocator;
|
||||
import std::io;
|
||||
|
||||
// global style, similar to the css box model
|
||||
struct Style { // css box model
|
||||
Rect padding;
|
||||
Rect border;
|
||||
Rect margin;
|
||||
|
||||
Color bg; // background color
|
||||
Color fg; // foreground color
|
||||
Color primary; // primary color
|
||||
Color secondary; // secondary color
|
||||
Color accent; // accent color
|
||||
|
||||
ushort radius;
|
||||
short size;
|
||||
}
|
||||
|
||||
const Style DEFAULT_STYLE = {
|
||||
.margin = {2, 2, 2, 2},
|
||||
.border = {2, 2, 2, 2},
|
||||
.padding = {1, 1, 1, 1},
|
||||
.radius = 0,
|
||||
.size = 16,
|
||||
|
||||
.bg = 0x282828ffu.@to_rgba(),
|
||||
.fg = 0xfbf1c7ffu.@to_rgba(),
|
||||
.primary = 0xcc241dffu.@to_rgba(),
|
||||
.secondary = 0x458588ffu.@to_rgba(),
|
||||
.accent = 0xfabd2fffu.@to_rgba(),
|
||||
};
|
||||
|
||||
// style is stored in a hashmap, each style has an Id that can be generated by a string or whatever
|
||||
alias StyleMap = map::HashMap{Id, Style};
|
||||
|
||||
// push or update a new style into the map
|
||||
fn void StyleMap.register_style(&map, Style* style, Id id)
|
||||
{
|
||||
if (style == null) return;
|
||||
map.set(id, *style);
|
||||
}
|
||||
|
||||
// get a style from the map, if the style is not found then use a default style.
|
||||
fn Style* StyleMap.get_style(&map, Id id)
|
||||
{
|
||||
Style*? s = map.get_ref(id);
|
||||
if (catch s) {
|
||||
// io::eprintfn("WARNING: style %x not found, using default style", id);
|
||||
return &DEFAULT_STYLE;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
fn int StyleMap.import_style_string(&map, String text)
|
||||
{
|
||||
Parser p;
|
||||
p.lex.text = text;
|
||||
int added;
|
||||
|
||||
while (p.parse_style() == true) {
|
||||
added++;
|
||||
// set the default style correctly
|
||||
map.register_style(&p.style, p.style_id);
|
||||
if (p.lex.peep_token().type == EOF) break;
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
fn int Ctx.import_style_from_string(&ctx, String text) => ctx.styles.import_style_string(text);
|
||||
fn int Ctx.import_style_from_file(&ctx, String path)
|
||||
{
|
||||
char[] text;
|
||||
usz size = file::get_size(path)!!;
|
||||
text = mem::new_array(char, size);
|
||||
file::load_buffer(path, text)!!;
|
||||
defer mem::free(text);
|
||||
|
||||
int added = ctx.import_style_from_string((String)text);
|
||||
return added;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Style can be serialized and deserialized with a subset of CSS
|
||||
* <style name> {
|
||||
* padding: left right top bottom;
|
||||
* border: left right top bottom;
|
||||
* margin: left right top bottom;
|
||||
* radius: uint;
|
||||
* size: uint;
|
||||
* Color: #RRGGBBAA;
|
||||
* Color: #RRGGBBAA;
|
||||
* Color: #RRGGBBAA;
|
||||
* Color: #RRGGBBAA;
|
||||
* Color: #RRGGBBAA;
|
||||
* }
|
||||
* The field "style name" will be hashed and the hash used as the id int the style map.
|
||||
* Fields may be excluded, each excluded field is set to zero.
|
||||
* The default unit is pixels, but millimeters is also available. The parser function accepts a scale
|
||||
* factor that has to be obtained with the window manager functions.
|
||||
*/
|
||||
// TODO: implement <style name> : <another style> to easily inherit all properties
|
||||
// of a previously defined style
|
||||
|
||||
module ugui::css;
|
||||
|
||||
import std::ascii;
|
||||
import std::io;
|
||||
|
||||
// CSS parser module
|
||||
|
||||
enum TokenType {
|
||||
INVALID,
|
||||
IDENTIFIER,
|
||||
RCURLY,
|
||||
LCURLY,
|
||||
SEMICOLON,
|
||||
COLON,
|
||||
NUMBER,
|
||||
COLOR,
|
||||
EOF,
|
||||
}
|
||||
|
||||
enum Unit {
|
||||
PIXELS,
|
||||
MILLIMETERS
|
||||
}
|
||||
|
||||
struct Token {
|
||||
TokenType type;
|
||||
usz line, col, off;
|
||||
String text;
|
||||
union {
|
||||
struct {
|
||||
float value;
|
||||
Unit unit;
|
||||
}
|
||||
Color color;
|
||||
}
|
||||
}
|
||||
|
||||
fn short Token.to_px(&t, float mm_to_px)
|
||||
{
|
||||
if (t.type != NUMBER) {
|
||||
unreachable("WFT you cannot convert to pixels a non-number");
|
||||
}
|
||||
if (t.unit == PIXELS) return (short)(t.value);
|
||||
return (short)(t.value * mm_to_px);
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
// LEXER //
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
|
||||
|
||||
struct Lexer {
|
||||
String text;
|
||||
usz line, col, off;
|
||||
}
|
||||
|
||||
macro char Lexer.peep(&lex) => lex.text[lex.off];
|
||||
|
||||
fn char Lexer.advance(&lex)
|
||||
{
|
||||
if (lex.off >= lex.text.len) return '\0';
|
||||
|
||||
char c = lex.text[lex.off];
|
||||
if (c == '\n') {
|
||||
lex.col = 0;
|
||||
lex.line++;
|
||||
} else {
|
||||
lex.col++;
|
||||
}
|
||||
lex.off++;
|
||||
return c;
|
||||
}
|
||||
|
||||
fn Token Lexer.next_token(&lex)
|
||||
{
|
||||
Token t;
|
||||
t.type = INVALID;
|
||||
t.off = lex.off;
|
||||
t.col = lex.col;
|
||||
t.line = lex.line;
|
||||
|
||||
if (lex.off >= lex.text.len) {
|
||||
return {.type = EOF};
|
||||
}
|
||||
|
||||
// skip whitespace
|
||||
while (ascii::is_space_m(lex.peep())) {
|
||||
if (lex.advance() == 0) return {.type = EOF};
|
||||
if (lex.off >= lex.text.len) return {.type = EOF};
|
||||
}
|
||||
t.off = lex.off;
|
||||
|
||||
switch (true) {
|
||||
case ascii::is_punct_m(lex.peep()) && lex.peep() != '#': // punctuation
|
||||
t.text = lex.text[lex.off:1];
|
||||
if (lex.advance() == 0) { t.type = INVALID; break; }
|
||||
switch (t.text[0]) {
|
||||
case ':': t.type = COLON;
|
||||
case ';': t.type = SEMICOLON;
|
||||
case '{': t.type = LCURLY;
|
||||
case '}': t.type = RCURLY;
|
||||
default: t.type = INVALID;
|
||||
}
|
||||
|
||||
case lex.peep() == '#': // color
|
||||
t.type = COLOR;
|
||||
if (lex.advance() == 0) { t.type = INVALID; break; }
|
||||
usz hex_start = t.off+1;
|
||||
while (ascii::is_alnum_m(lex.peep())) {
|
||||
if (lex.advance() == 0) { t.type = INVALID; break; }
|
||||
}
|
||||
usz h_len = lex.off - hex_start;
|
||||
if (h_len != 8 && h_len != 6) {
|
||||
io::eprintfn("CSS lexing error at %d:%d: the only suppported color formats are #RRGGBBAA or #RRGGBB", t.line, t.col);
|
||||
t.type = INVALID;
|
||||
break;
|
||||
}
|
||||
|
||||
char[10] hex_str = (char[])"0x";
|
||||
hex_str[2:h_len] = lex.text[hex_start:h_len];
|
||||
if (h_len == 6) hex_str[8..9] = "ff"[..];
|
||||
uint? color_hex = ((String)hex_str[..]).to_uint();
|
||||
if (catch color_hex) {
|
||||
t.type = INVALID;
|
||||
break;
|
||||
}
|
||||
t.color = color_hex.to_rgba();
|
||||
|
||||
case ascii::is_alpha_m(lex.peep()): // identifier
|
||||
t.type = IDENTIFIER;
|
||||
while (ascii::is_alnum_m(lex.peep()) || lex.peep() == '-' || lex.peep() == '_') {
|
||||
if (lex.advance() == 0) { t.type = INVALID; break; }
|
||||
}
|
||||
t.text = lex.text[t.off..lex.off-1];
|
||||
|
||||
case ascii::is_digit_m(lex.peep()): // number
|
||||
t.type = NUMBER;
|
||||
t.unit = PIXELS;
|
||||
// find the end of the number
|
||||
usz end;
|
||||
while (ascii::is_alnum_m(lex.peep()) || lex.peep() == '+' || lex.peep() == '-' || lex.peep() == '.') {
|
||||
if (lex.advance() == 0) { t.type = INVALID; break; }
|
||||
}
|
||||
end = lex.off;
|
||||
if (end - t.off > 2) {
|
||||
if (lex.text[end-2:2] == "px") {
|
||||
t.unit = PIXELS;
|
||||
end -= 2;
|
||||
} else if (lex.text[end-2:2] == "mm") {
|
||||
t.unit = MILLIMETERS;
|
||||
end -= 2;
|
||||
} else if (lex.text[end-2:2] == "pt" || lex.text[end-2:2] == "em") {
|
||||
io::eprintn("units 'em' or 'pt' are not supported at the moment");
|
||||
t.type = INVALID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
String number_str = lex.text[t.off..end-1];
|
||||
float? value = number_str.to_float();
|
||||
if (catch value) { t.type = INVALID; break; }
|
||||
t.value = value;
|
||||
t.text = lex.text[t.off..lex.off-1];
|
||||
}
|
||||
|
||||
if (t.type == INVALID) {
|
||||
io::eprintfn("CSS Lexing ERROR at %d:%d: '%s' is not a valid token", t.line, t.col, lex.text[t.off..lex.off]);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
fn Token Lexer.peep_token(&lex)
|
||||
{
|
||||
Lexer start_state = *lex;
|
||||
Token t;
|
||||
t = lex.next_token();
|
||||
*lex = start_state;
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
// PARSER //
|
||||
// ---------------------------------------------------------------------------------- //
|
||||
|
||||
|
||||
struct Parser {
|
||||
Lexer lex;
|
||||
Style style;
|
||||
Id style_id;
|
||||
float mm_to_px;
|
||||
}
|
||||
|
||||
macro bool Parser.expect(&p, Token* t, TokenType type)
|
||||
{
|
||||
*t = p.lex.next_token();
|
||||
if (t.type == type) return true;
|
||||
io::eprintfn("CSS parsing error at %d:%d: expected %s but got %s", t.line, t.col, type, t.type);
|
||||
return false;
|
||||
}
|
||||
|
||||
// style := ( IDENTIFIER "{" property_list "}" )
|
||||
// property_list := property property_list
|
||||
fn bool Parser.parse_style(&p)
|
||||
{
|
||||
Token t;
|
||||
p.style = {};
|
||||
p.style_id = 0;
|
||||
|
||||
// style name
|
||||
if (p.expect(&t, IDENTIFIER) == false) return false;
|
||||
p.style_id = t.text.hash();
|
||||
|
||||
// style body
|
||||
if (p.expect(&t, LCURLY) == false) return false;
|
||||
|
||||
while (true) {
|
||||
if (p.parse_property() == false) return false;
|
||||
t = p.lex.peep_token();
|
||||
if (t.type != IDENTIFIER) break;
|
||||
}
|
||||
|
||||
if (p.expect(&t, RCURLY) == false) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// property := ( number_property | color_property | size_property ) ";"
|
||||
// number_property := ( "radius" | "size" ) "=" number
|
||||
// color_property := ( "bg" | "fg" | "primary" | "secondary" | "accent" ) "=" color
|
||||
// size_property := ( "padding" | "border" | "margin" ) "=" size
|
||||
fn bool Parser.parse_property(&p)
|
||||
{
|
||||
Token t, prop;
|
||||
if (p.expect(&prop, IDENTIFIER) == false) return false;
|
||||
if (p.expect(&t, COLON) == false) return false;
|
||||
|
||||
switch (prop.text) {
|
||||
case "padding":
|
||||
Rect padding;
|
||||
if (p.parse_size(&padding) == false) return false;
|
||||
p.style.padding = padding;
|
||||
|
||||
case "border":
|
||||
Rect border;
|
||||
if (p.parse_size(&border) == false) return false;
|
||||
p.style.border = border;
|
||||
|
||||
case "margin":
|
||||
Rect margin;
|
||||
if (p.parse_size(&margin) == false) return false;
|
||||
p.style.margin = margin;
|
||||
|
||||
case "bg":
|
||||
Color bg;
|
||||
if (p.parse_color(&bg) == false) return false;
|
||||
p.style.bg = bg;
|
||||
|
||||
case "fg":
|
||||
Color fg;
|
||||
if (p.parse_color(&fg) == false) return false;
|
||||
p.style.fg = fg;
|
||||
|
||||
case "primary":
|
||||
Color primary;
|
||||
if (p.parse_color(&primary) == false) return false;
|
||||
p.style.primary = primary;
|
||||
|
||||
case "secondary":
|
||||
Color secondary;
|
||||
if (p.parse_color(&secondary) == false) return false;
|
||||
p.style.secondary = secondary;
|
||||
|
||||
case "accent":
|
||||
Color accent;
|
||||
if (p.parse_color(&accent) == false) return false;
|
||||
p.style.accent = accent;
|
||||
|
||||
case "radius":
|
||||
short r;
|
||||
if (p.parse_number(&r) == false) return false;
|
||||
if (r < 0) {
|
||||
io::eprintfn("CSS parsing error at %d:%d: 'radius' must be a positive number, got %d", t.line, t.col, r);
|
||||
return false;
|
||||
}
|
||||
p.style.radius = (ushort)r;
|
||||
|
||||
case "size":
|
||||
short s;
|
||||
if (p.parse_number(&s) == false) return false;
|
||||
if (s < 0) {
|
||||
io::eprintfn("CSS parsing error at %d:%d: 'size' must be a positive number, got %d", t.line, t.col, s);
|
||||
return false;
|
||||
}
|
||||
p.style.size = (ushort)s;
|
||||
|
||||
|
||||
default:
|
||||
io::eprintfn("CSS parsing error at %d:%d: '%s' is not a valid property", prop.line, prop.col, prop.text);
|
||||
return false;
|
||||
}
|
||||
if (p.expect(&t, SEMICOLON) == false) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// number := NUMBER
|
||||
fn bool Parser.parse_number(&p, short* n)
|
||||
{
|
||||
Token t;
|
||||
if (p.expect(&t, NUMBER) == false) return false;
|
||||
*n = t.to_px(p.mm_to_px);
|
||||
return true;
|
||||
}
|
||||
|
||||
// color := COLOR
|
||||
// FIXME: since '#' is punctuation this cannot be done in parsing but it has to be done in lexing
|
||||
fn bool Parser.parse_color(&p, Color* c)
|
||||
{
|
||||
Token t;
|
||||
if (p.expect(&t, COLOR) == false) return false;
|
||||
*c = t.color;
|
||||
return true;
|
||||
}
|
||||
|
||||
// size := number | number_list
|
||||
// number_list := number number number number
|
||||
fn bool Parser.parse_size(&p, Rect* r)
|
||||
{
|
||||
short x;
|
||||
Token t;
|
||||
|
||||
if (p.parse_number(&x) == false) return false;
|
||||
|
||||
t = p.lex.peep_token();
|
||||
if (t.type == NUMBER) {
|
||||
// we got another number so we expect three more
|
||||
r.x = x;
|
||||
if (p.parse_number(&x) == false) return false;
|
||||
r.y = x;
|
||||
if (p.parse_number(&x) == false) return false;
|
||||
r.w = x;
|
||||
if (p.parse_number(&x) == false) return false;
|
||||
r.h = x;
|
||||
return true;
|
||||
} else if (t.type == SEMICOLON) {
|
||||
// just one number, all dimensions are the same
|
||||
r.x = r.y = r.w = r.h = x;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
288
src/textedit.c3
Normal file
288
src/textedit.c3
Normal file
@ -0,0 +1,288 @@
|
||||
module ugui;
|
||||
|
||||
import ugui::textedit::te;
|
||||
|
||||
struct TextEdit {
|
||||
char[] buffer;
|
||||
usz chars;
|
||||
usz cursor;
|
||||
isz sel_len;
|
||||
}
|
||||
|
||||
fn String TextEdit.to_string(&te) => (String)te.buffer[:te.chars];
|
||||
fn String TextEdit.until_cursor(&te) => (String)te.buffer[..te.cursor];
|
||||
fn String TextEdit.from_cursor(&te) => (String)te.buffer[te.cursor..te.chars];
|
||||
fn String TextEdit.until(&te, usz off) => (String)te.buffer[..min(te.chars, off)];
|
||||
fn String TextEdit.from(&te, usz off) => (String)te.buffer[off..te.chars];
|
||||
|
||||
// implement text editing operations on the buffer
|
||||
// returns true if the buffer is full
|
||||
fn bool Ctx.text_edit(&ctx, TextEdit* te)
|
||||
{
|
||||
String in = ctx.get_keys();
|
||||
ModKeys mod = ctx.get_mod();
|
||||
|
||||
te.insert_utf8(in);
|
||||
|
||||
bool select = !!(mod & KMOD_SHIFT);
|
||||
bool ctrl = !!(mod & KMOD_CTRL);
|
||||
|
||||
// handle backspace and delete
|
||||
if (mod.bkspc) {
|
||||
if (ctrl) {
|
||||
te.remove_word(false);
|
||||
} else {
|
||||
te.remove_character(false);
|
||||
}
|
||||
}
|
||||
if (mod.del) {
|
||||
if (ctrl) {
|
||||
te.remove_word(true);
|
||||
} else {
|
||||
te.remove_character(true);
|
||||
}
|
||||
}
|
||||
|
||||
// handle arrow keys
|
||||
if (mod.left) {
|
||||
if (ctrl) {
|
||||
te.move_cursor_word(false, select);
|
||||
} else {
|
||||
te.move_cursor(false, select);
|
||||
}
|
||||
}
|
||||
if (mod.right) {
|
||||
if (ctrl) {
|
||||
te.move_cursor_word(true, select);
|
||||
} else {
|
||||
te.move_cursor(true, select);
|
||||
}
|
||||
}
|
||||
|
||||
if (mod.home) {
|
||||
te.set_cursor(te.search_lines(false).first, select);
|
||||
}
|
||||
|
||||
if (mod.end) {
|
||||
te.set_cursor(te.search_lines(true).first, select);
|
||||
}
|
||||
|
||||
// TODO: up, down
|
||||
|
||||
return te.chars < te.buffer.len;
|
||||
}
|
||||
|
||||
module ugui::textedit::te;
|
||||
|
||||
import std::core::string;
|
||||
import std::ascii;
|
||||
import std::collections::pair;
|
||||
|
||||
alias OffPair = pair::Pair{usz, usz};
|
||||
|
||||
// returns the offset of the next codepoint in the buffer from the cursor
|
||||
fn usz TextEdit.next_char_off(&te)
|
||||
{
|
||||
usz len = min(te.chars - te.cursor, 4);
|
||||
if (len == 0) return len;
|
||||
conv::utf8_to_char32(&te.buffer[te.cursor], &len)!!;
|
||||
return len;
|
||||
}
|
||||
|
||||
// returns the offset of the previous codepoint in the buffer from the cursor
|
||||
fn usz TextEdit.prev_char_off(&te)
|
||||
{
|
||||
if (te.cursor == 0) return 0;
|
||||
String b = (String)te.buffer[..te.cursor];
|
||||
usz len;
|
||||
foreach_r (off, c: b) {
|
||||
if (c & 0xC0 == 0x80) continue;
|
||||
len = b.len - off;
|
||||
break;
|
||||
}
|
||||
// verify the utf8 character
|
||||
conv::utf8_to_char32(&b[te.cursor - len], &len)!!;
|
||||
return len;
|
||||
}
|
||||
|
||||
// moves the cursor forwards or backwards by one codepoint without exiting the bounds, if select is
|
||||
// true also change the selection width
|
||||
fn void TextEdit.move_cursor(&te, bool forward, bool select)
|
||||
{
|
||||
// in selection but trying to move without selecting, snap the cursor and reset selection
|
||||
if (te.sel_len != 0 && !select) {
|
||||
if (te.sel_len > 0 && forward) {
|
||||
// selection is in front of the cursor and trying to move right, snap the cursor to the
|
||||
// end of selection
|
||||
te.cursor += te.sel_len;
|
||||
} else if (te.sel_len < 0 && !forward) {
|
||||
// selection is behind the cursor and trying to move left, snap the cursor to the start
|
||||
// of selection
|
||||
te.cursor += te.sel_len;
|
||||
}
|
||||
te.sel_len = 0;
|
||||
|
||||
} else {
|
||||
isz off = forward ? te.next_char_off() : -te.prev_char_off();
|
||||
if (te.cursor + off < 0 || te.cursor + off > te.chars) return;
|
||||
te.cursor += off;
|
||||
// if trying to select increment selection width
|
||||
if (select) {
|
||||
te.sel_len -= off;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set the cursor to the exact offset provided, the selection flags controls wether the selection has
|
||||
// expanded or rese
|
||||
fn void TextEdit.set_cursor(&te, usz cur, bool select)
|
||||
{
|
||||
if (!select) te.sel_len = 0;
|
||||
if (cur == te.cursor) return;
|
||||
|
||||
usz prev_cur = te.cursor;
|
||||
te.cursor = cur;
|
||||
if (select) {
|
||||
te.sel_len += prev_cur - cur;
|
||||
}
|
||||
}
|
||||
|
||||
fn void TextEdit.move_cursor_word(&te, bool forward, bool select)
|
||||
{
|
||||
// moving out of selection, snap to the end
|
||||
if (!select && te.sel_len != 0) {
|
||||
te.move_cursor(forward, select);
|
||||
return;
|
||||
}
|
||||
|
||||
usz prev_cur = te.cursor;
|
||||
while (te.cursor <= te.chars) {
|
||||
char c;
|
||||
if (forward) {
|
||||
if (te.cursor == te.chars) break;
|
||||
c = te.buffer[te.cursor];
|
||||
} else {
|
||||
if (te.cursor == 0) break;
|
||||
c = te.buffer[te.cursor-1];
|
||||
}
|
||||
if (ascii::is_space(c) || ascii::is_punct(c)) break;
|
||||
te.move_cursor(forward, select);
|
||||
}
|
||||
|
||||
// move at least one character
|
||||
if (prev_cur == te.cursor) te.move_cursor(forward, select);
|
||||
}
|
||||
|
||||
// get the offset of the current line start and the previous line start from the cursor
|
||||
fn OffPair TextEdit.search_lines(&te, bool forward)
|
||||
{
|
||||
// look for the current line start
|
||||
OffPair p;
|
||||
|
||||
if (forward) {
|
||||
// if searching forwards look for the end of the current line and the end of the next line
|
||||
p.first = te.cursor + (te.from_cursor().index_of_char('\n') ?? (te.chars - te.cursor));
|
||||
if (p.first < te.chars) {
|
||||
p.second = p.first + 1 + (te.from(p.first + 1).index_of_char('\n') ?? (te.chars - p.first - 1));
|
||||
} else {
|
||||
p.second = te.chars;
|
||||
}
|
||||
} else {
|
||||
// if searching backwards, look for the start of the current and previous line
|
||||
if (te.cursor > 0) {
|
||||
p.first = te.until(te.cursor-1).rindex_of_char('\n') ?? 0;
|
||||
if (p.first != 0 && te.chars) p.first++;
|
||||
}
|
||||
p.second = te.until(p.first).rindex_of_char('\n') ?? 0;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
fn void TextEdit.delete_selection(&te)
|
||||
{
|
||||
if (te.sel_len == 0) return;
|
||||
|
||||
usz start = te.sel_len > 0 ? te.cursor : te.cursor + te.sel_len;
|
||||
usz end = te.sel_len > 0 ? te.cursor + te.sel_len : te.cursor;
|
||||
usz len = te.chars - end;
|
||||
|
||||
te.buffer[start:len] = te.buffer[end:len];
|
||||
te.cursor -= te.sel_len < 0 ? -te.sel_len : 0;
|
||||
te.chars -= te.sel_len < 0 ? -te.sel_len : te.sel_len;
|
||||
te.sel_len = 0;
|
||||
}
|
||||
|
||||
// removes the character before or after the cursor
|
||||
fn void TextEdit.remove_character(&te, bool forward)
|
||||
{
|
||||
// if there is a selection active then delete that selection
|
||||
if (te.sel_len) {
|
||||
te.delete_selection();
|
||||
return;
|
||||
}
|
||||
|
||||
if (te.chars == 0) return;
|
||||
if (forward) {
|
||||
usz rem = te.chars - te.cursor;
|
||||
if (rem > 0) {
|
||||
usz len = te.next_char_off();
|
||||
te.buffer[te.cursor:rem-len] = te.buffer[te.cursor+len:rem-len];
|
||||
te.chars -= len;
|
||||
}
|
||||
} else {
|
||||
if (te.cursor > 0) {
|
||||
usz len = te.prev_char_off();
|
||||
te.buffer[te.cursor-len:te.chars-te.cursor] = te.buffer[te.cursor:te.chars-te.cursor];
|
||||
te.chars -= len;
|
||||
te.cursor -= len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove the word before or after the cursor up until the next punctuation or space
|
||||
fn void TextEdit.remove_word(&te, bool forward)
|
||||
{
|
||||
// if there is a selection active then delete that selection
|
||||
if (te.sel_len) {
|
||||
te.delete_selection();
|
||||
return;
|
||||
}
|
||||
|
||||
usz prev_cur = te.cursor;
|
||||
while (te.chars > 0) {
|
||||
char c;
|
||||
if (forward) {
|
||||
if (te.cursor == te.chars) break;
|
||||
c = te.buffer[te.cursor];
|
||||
} else {
|
||||
if (te.cursor == 0) break;
|
||||
c = te.buffer[te.cursor-1];
|
||||
}
|
||||
if (ascii::is_space(c) || ascii::is_punct(c)) break;
|
||||
te.remove_character(forward);
|
||||
}
|
||||
|
||||
// delete at least one character
|
||||
if (prev_cur == te.cursor) te.remove_character(forward);
|
||||
}
|
||||
|
||||
// insert a character at the cursor and update the cursor
|
||||
fn void TextEdit.insert_character(&te, uint cp)
|
||||
{
|
||||
char[4] b;
|
||||
usz len = conv::char32_to_utf8(cp, b[..])!!;
|
||||
te.insert_utf8((String)b[:len]);
|
||||
}
|
||||
|
||||
fn void TextEdit.insert_utf8(&te, String s)
|
||||
{
|
||||
if (s.len == 0) return;
|
||||
if (s.len + te.chars > te.buffer.len) return;
|
||||
|
||||
te.delete_selection();
|
||||
te.buffer[te.cursor+s.len : te.chars-te.cursor] = te.buffer[te.cursor : te.chars-te.cursor];
|
||||
te.buffer[te.cursor : s.len] = s[..];
|
||||
te.chars += s.len;
|
||||
te.cursor += s.len;
|
||||
}
|
||||
226
src/widgets/button.c3
Normal file
226
src/widgets/button.c3
Normal file
@ -0,0 +1,226 @@
|
||||
module ugui;
|
||||
|
||||
import std::io;
|
||||
|
||||
// button element
|
||||
struct ElemButton {
|
||||
int filler;
|
||||
}
|
||||
|
||||
|
||||
macro Ctx.button(&ctx, String label = "", String icon = "", ...)
|
||||
=> ctx.button_id(@compute_id($vasplat), label, icon);
|
||||
fn ElemEvents? Ctx.button_id(&ctx, Id id, String label, String icon)
|
||||
{
|
||||
id = ctx.gen_id(id)!;
|
||||
Elem* parent, elem;
|
||||
ctx.get_elem(id, ETYPE_BUTTON)!.unpack(&elem, &parent);
|
||||
Style* style = ctx.styles.get_style(@str_hash("button"));
|
||||
|
||||
Sprite* sprite = icon != "" ? ctx.sprite_atlas.get(icon)! : &&(Sprite){};
|
||||
Rect icon_size = sprite.rect();
|
||||
|
||||
ushort min_size = style.size;
|
||||
ushort half_lh = (ushort)(ctx.font.line_height() / 2);
|
||||
ushort inner_pad = label != "" && icon != "" ? half_lh : 0;
|
||||
/*
|
||||
* +--------------------------------------+
|
||||
* | +--------+ |
|
||||
* | | | +-----------------+ |
|
||||
* | | icon | | label | |
|
||||
* | | | +-----------------+ |
|
||||
* | +--------+<->| |
|
||||
* +-------------^------------------------+
|
||||
* |inner_pad
|
||||
*/
|
||||
Point content_size = {
|
||||
.x = icon_size.w + inner_pad, // text sizing is handled differently
|
||||
.y = icon_size.h + inner_pad,
|
||||
};
|
||||
elem.layout.w = @fit(min_size);
|
||||
elem.layout.h = @fit(min_size);
|
||||
elem.layout.children.w = @exact(content_size.x);
|
||||
elem.layout.children.h = @exact(content_size.y);
|
||||
elem.layout.text = ctx.measure_string(label)!;
|
||||
elem.layout.content_offset = style.margin + style.border + style.padding;
|
||||
|
||||
update_parent_size(elem, parent);
|
||||
|
||||
//elem.events = ctx.get_elem_events(elem);
|
||||
// if (ctx.input.z_index == elem.z_index) println("true ", elem.z_index);
|
||||
Rect content_bounds = elem.content_bounds();
|
||||
|
||||
Rect icon_bounds = {
|
||||
.x = content_bounds.x,
|
||||
.y = content_bounds.y,
|
||||
.w = icon_size.w,
|
||||
.h = icon_size.h
|
||||
};
|
||||
icon_bounds = icon_size.center_to(icon_bounds);
|
||||
|
||||
Rect text_bounds = {
|
||||
.x = content_bounds.x + icon_bounds.w + inner_pad,
|
||||
.y = content_bounds.y,
|
||||
.w = content_bounds.w - icon_bounds.w - inner_pad,
|
||||
.h = content_bounds.h,
|
||||
};
|
||||
//text_bounds = text_size.center_to(text_bounds);
|
||||
|
||||
bool is_active = elem.events.has_focus || elem.events.mouse_hover;
|
||||
Style s = *style;
|
||||
if (is_active) {
|
||||
s.secondary = s.primary;
|
||||
s.bg = s.accent;
|
||||
}
|
||||
|
||||
ctx.push_rect(elem.bounds.pad(style.margin), parent.z_index, &s)!;
|
||||
if (icon != "") {
|
||||
ctx.push_sprite(icon_bounds, sprite.uv(), ctx.sprite_atlas.id, parent.z_index, type: sprite.type)!;
|
||||
}
|
||||
if (label != "") {
|
||||
ctx.layout_string(label, text_bounds, CENTER, parent.z_index, style.fg)!;
|
||||
}
|
||||
return elem.events;
|
||||
}
|
||||
|
||||
|
||||
macro Ctx.checkbox(&ctx, String desc, bool* active, String tick_sprite = "", ...)
|
||||
=> ctx.checkbox_id(@compute_id($vasplat), desc, active, tick_sprite);
|
||||
fn void? Ctx.checkbox_id(&ctx, Id id, String description, bool* active, String tick_sprite)
|
||||
{
|
||||
id = ctx.gen_id(id)!;
|
||||
Elem* parent, elem;
|
||||
ctx.get_elem(id, ETYPE_BUTTON)!.unpack(&elem, &parent);
|
||||
Style* style = ctx.styles.get_style(@str_hash("checkbox"));
|
||||
|
||||
short inner_pad = description != "" ? style.size/2 : 0;
|
||||
/*
|
||||
* |< >| style.size/2
|
||||
* +---------------------|---|-----------+
|
||||
* | | .-----. ---|--
|
||||
* | +-----------------+ ' ### ' | ^
|
||||
* | | description | | ##### | | style.size
|
||||
* | +-----------------+ . ### . | v
|
||||
* | '-----' ---|--
|
||||
* +-------------------------|-------|---+
|
||||
* |<----->| style.size
|
||||
*/
|
||||
|
||||
elem.layout.w = @fit(style.size);
|
||||
elem.layout.h = @fit(style.size);
|
||||
elem.layout.children.w = @exact(style.size + inner_pad);
|
||||
elem.layout.children.h = @exact(style.size);
|
||||
elem.layout.text = ctx.measure_string(description)!;
|
||||
elem.layout.content_offset = style.margin + style.border + style.padding;
|
||||
|
||||
update_parent_size(elem, parent);
|
||||
|
||||
//elem.events = ctx.get_elem_events(elem);
|
||||
if (elem.events.mouse_hover && elem.events.mouse_release) *active = !(*active);
|
||||
|
||||
|
||||
Rect content_bounds = elem.bounds.pad(elem.layout.content_offset);
|
||||
Rect text_bounds = {
|
||||
.x = content_bounds.x,
|
||||
.y = content_bounds.y,
|
||||
.w = content_bounds.w - inner_pad - style.size,
|
||||
.h = content_bounds.h
|
||||
};
|
||||
Rect check_bounds = {
|
||||
.x = content_bounds.x + text_bounds.w + inner_pad,
|
||||
.y = content_bounds.y + (content_bounds.h - style.size)/2,
|
||||
.w = style.size,
|
||||
.h = style.size,
|
||||
};
|
||||
|
||||
Style s;
|
||||
s.bg = style.bg;
|
||||
s.secondary = style.secondary;
|
||||
s.border = style.border;
|
||||
s.radius = style.radius;
|
||||
|
||||
ctx.layout_string(description, text_bounds, CENTER, parent.z_index, style.fg)!;
|
||||
if (tick_sprite != "") {
|
||||
ctx.push_rect(check_bounds, parent.z_index, &s)!;
|
||||
if (*active) {
|
||||
Sprite* sprite = ctx.sprite_atlas.get(tick_sprite)!;
|
||||
Id tex_id = ctx.sprite_atlas.id;
|
||||
ctx.push_sprite(check_bounds, sprite.uv(), tex_id, parent.z_index, type: sprite.type)!;
|
||||
}
|
||||
} else {
|
||||
if (*active) {
|
||||
s.bg = style.primary;
|
||||
ctx.push_rect(check_bounds, parent.z_index, &s)!;
|
||||
} else {
|
||||
ctx.push_rect(check_bounds, parent.z_index, &s)!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro Ctx.toggle(&ctx, String desc, bool* active)
|
||||
=> ctx.toggle_id(@compute_id($vasplat), desc, active);
|
||||
fn void? Ctx.toggle_id(&ctx, Id id, String description, bool* active)
|
||||
{
|
||||
id = ctx.gen_id(id)!;
|
||||
|
||||
Elem* parent, elem;
|
||||
ctx.get_elem(id, ETYPE_BUTTON)!.unpack(&elem, &parent);
|
||||
Style* style = ctx.styles.get_style(@str_hash("toggle"));
|
||||
|
||||
short inner_pad = description != "" ? style.size/2 : 0;
|
||||
/*
|
||||
* |< >| style.size/2
|
||||
* +---------------------|---|-----------------+
|
||||
* | | .-----------. ---|--
|
||||
* | +-----------------+ ' ##### ' | ^
|
||||
* | | description | | ##### | | style.size
|
||||
* | +-----------------+ . ##### . | v
|
||||
* | '-----------' ---|--
|
||||
* +-------------------------|-------------|---+
|
||||
* |<----->| style.size*2
|
||||
*/
|
||||
|
||||
elem.layout.w = @fit(style.size*2);
|
||||
elem.layout.h = @fit(style.size);
|
||||
elem.layout.children.w = @exact(style.size*2 + inner_pad);
|
||||
elem.layout.children.h = @exact(style.size);
|
||||
elem.layout.text = ctx.measure_string(description)!;
|
||||
elem.layout.content_offset = style.margin + style.border + style.padding;
|
||||
|
||||
update_parent_size(elem, parent);
|
||||
|
||||
//elem.events = ctx.get_elem_events(elem);
|
||||
if (elem.events.mouse_hover && elem.events.mouse_release) *active = !(*active);
|
||||
|
||||
Rect content_bounds = elem.bounds.pad(elem.layout.content_offset);
|
||||
Rect text_bounds = {
|
||||
.x = content_bounds.x,
|
||||
.y = content_bounds.y,
|
||||
.w = content_bounds.w - inner_pad - style.size*2,
|
||||
.h = content_bounds.h
|
||||
};
|
||||
Rect toggle_bounds = {
|
||||
.x = content_bounds.x + text_bounds.w + inner_pad,
|
||||
.y = content_bounds.y + (content_bounds.h - style.size)/2,
|
||||
.w = style.size*2,
|
||||
.h = style.size,
|
||||
};
|
||||
Rect toggle = {
|
||||
.x = toggle_bounds.x + (*active ? style.size : 0),
|
||||
.y = toggle_bounds.y,
|
||||
.w = style.size,
|
||||
.h = style.size
|
||||
};
|
||||
|
||||
Style s;
|
||||
s.bg = style.bg;
|
||||
s.secondary = style.secondary;
|
||||
s.border = style.border;
|
||||
s.radius = style.radius;
|
||||
ctx.layout_string(description, text_bounds, CENTER, parent.z_index, style.fg)!;
|
||||
ctx.push_rect(toggle_bounds, parent.z_index, &s)!;
|
||||
s.bg = style.primary;
|
||||
s.border = {};
|
||||
ctx.push_rect(toggle.pad(style.border), parent.z_index, &s)!;
|
||||
|
||||
}
|
||||
252
src/widgets/div.c3
Normal file
252
src/widgets/div.c3
Normal file
@ -0,0 +1,252 @@
|
||||
module ugui;
|
||||
|
||||
import std::io;
|
||||
import std::math;
|
||||
|
||||
// div element
|
||||
struct ElemDiv {
|
||||
struct scroll_x {
|
||||
bool enabled;
|
||||
bool on;
|
||||
float value;
|
||||
}
|
||||
struct scroll_y {
|
||||
bool enabled;
|
||||
bool on;
|
||||
float value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
macro Ctx.@center(&ctx, LayoutDirection dir = ROW, ...; @body())
|
||||
{
|
||||
return ctx.@div(@grow(), @grow(), dir, CENTER) {
|
||||
@body();
|
||||
}!;
|
||||
}
|
||||
|
||||
macro Ctx.@row(&ctx, Anchor anchor = TOP_LEFT, ...; @body())
|
||||
{
|
||||
return ctx.@div(@fit(), @fit(), ROW, anchor: anchor) {
|
||||
@body();
|
||||
}!;
|
||||
}
|
||||
|
||||
macro Ctx.@column(&ctx, Anchor anchor = TOP_LEFT, ...; @body())
|
||||
{
|
||||
return ctx.@div(@fit(), @fit(), COLUMN, anchor: anchor) {
|
||||
@body();
|
||||
}!;
|
||||
}
|
||||
|
||||
|
||||
// useful macro to start and end a div, capturing the trailing block
|
||||
macro Ctx.@div(&ctx,
|
||||
Size width = @grow, Size height = @grow,
|
||||
LayoutDirection dir = ROW,
|
||||
Anchor anchor = TOP_LEFT,
|
||||
bool absolute = false, Point off = {},
|
||||
bool scroll_x = false, bool scroll_y = false,
|
||||
...;
|
||||
@body()
|
||||
)
|
||||
{
|
||||
ctx.div_begin(width, height, dir, anchor, absolute, off, scroll_x, scroll_y, $vasplat)!;
|
||||
@body();
|
||||
return ctx.div_end()!;
|
||||
}
|
||||
|
||||
macro Ctx.div_begin(&ctx,
|
||||
Size width = @grow(), Size height = @grow(),
|
||||
LayoutDirection dir = ROW,
|
||||
Anchor anchor = TOP_LEFT,
|
||||
bool absolute = false, Point off = {},
|
||||
bool scroll_x = false, bool scroll_y = false,
|
||||
...
|
||||
)
|
||||
{
|
||||
return ctx.div_begin_id(@compute_id($vasplat), width, height, dir, anchor, absolute, off, scroll_x, scroll_y);
|
||||
}
|
||||
|
||||
fn void? Ctx.div_begin_id(&ctx,
|
||||
Id id,
|
||||
Size width, Size height,
|
||||
LayoutDirection dir,
|
||||
Anchor anchor,
|
||||
bool absolute, Point off,
|
||||
bool scroll_x, bool scroll_y
|
||||
)
|
||||
{
|
||||
id = ctx.gen_id(id)!;
|
||||
|
||||
Elem* parent, elem;
|
||||
ctx.get_elem(id, ETYPE_DIV)!.unpack(&elem, &parent);
|
||||
ctx.active_div = elem.tree_idx;
|
||||
|
||||
Style* style = ctx.styles.get_style(@str_hash("div"));
|
||||
|
||||
elem.div.scroll_x.enabled = scroll_x;
|
||||
elem.div.scroll_y.enabled = scroll_y;
|
||||
|
||||
// update layout with correct info
|
||||
elem.layout = {
|
||||
.w = width,
|
||||
.h = height,
|
||||
.dir = dir,
|
||||
.anchor = anchor,
|
||||
.content_offset = style.margin + style.border + style.padding,
|
||||
.absolute = absolute,
|
||||
};
|
||||
if (absolute) {
|
||||
elem.layout.origin.x = off.x;
|
||||
elem.layout.origin.y = off.y;
|
||||
}
|
||||
|
||||
ctx.push_rect(elem.bounds.pad(style.margin), elem.z_index, style)!;
|
||||
|
||||
// update the ctx scissor, it HAS to be after drawing the background
|
||||
ctx.div_scissor = elem.bounds.pad(elem.layout.content_offset).max({0,0,0,0});
|
||||
ctx.push_scissor(ctx.div_scissor, elem.z_index)!;
|
||||
|
||||
//elem.events = ctx.get_elem_events(elem);
|
||||
|
||||
// TODO: check active
|
||||
// TODO: check resizeable
|
||||
}
|
||||
|
||||
fn Id? Ctx.div_end(&ctx)
|
||||
{
|
||||
Elem* elem = ctx.get_active_div()!;
|
||||
Style* style = ctx.styles.get_style(@str_hash("div"));
|
||||
Rect bounds = elem.bounds.pad(style.margin + style.border);
|
||||
|
||||
// set the scrollbar flag, is used in layout
|
||||
Point cbc = elem.children_bounds.bottom_right();
|
||||
Point bc = elem.bounds.bottom_right();
|
||||
// horizontal overflow
|
||||
elem.div.scroll_x.on = cbc.x > bc.x && elem.div.scroll_x.enabled;
|
||||
// vertical overflow
|
||||
elem.div.scroll_y.on = cbc.y > bc.y && elem.div.scroll_y.enabled;
|
||||
|
||||
Id hsid_raw = @str_hash("div_scrollbar_horizontal");
|
||||
Id vsid_raw = @str_hash("div_scrollbar_vertical");
|
||||
Id hsid_real = ctx.gen_id(hsid_raw)!;
|
||||
Id vsid_real = ctx.gen_id(vsid_raw)!;
|
||||
|
||||
if (elem.div.scroll_y.on) {
|
||||
if (ctx.input.events.mouse_scroll && ctx.hover_id == elem.id && !(ctx.get_mod() & KMOD_SHIFT)) {
|
||||
elem.div.scroll_y.value += ctx.input.mouse.scroll.y * 0.07f;
|
||||
elem.div.scroll_y.value = math::clamp(elem.div.scroll_y.value, 0.0f, 1.0f);
|
||||
}
|
||||
ctx.scrollbar(vsid_raw, &elem.div.scroll_y.value, max((float)bc.y / cbc.y, (float)0.15))!;
|
||||
elem.layout.scroll_offset.y = (short)(elem.div.scroll_y.value*(float)(elem.children_bounds.h-elem.bounds.h));
|
||||
} else {
|
||||
elem.div.scroll_y.value = 0;
|
||||
}
|
||||
|
||||
|
||||
if (elem.div.scroll_x.on) {
|
||||
if (ctx.input.events.mouse_scroll && ctx.hover_id == elem.id) {
|
||||
if (ctx.get_mod() & KMOD_SHIFT) { // horizontal scroll with shift
|
||||
elem.div.scroll_x.value += ctx.input.mouse.scroll.y * 0.07f;
|
||||
elem.div.scroll_x.value = math::clamp(elem.div.scroll_x.value, 0.0f, 1.0f);
|
||||
} else {
|
||||
elem.div.scroll_x.value += ctx.input.mouse.scroll.x * 0.07f;
|
||||
elem.div.scroll_x.value = math::clamp(elem.div.scroll_x.value, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
ctx.scrollbar(hsid_raw, &elem.div.scroll_x.value, max((float)bc.x / cbc.x, (float)0.15), false)!;
|
||||
elem.layout.scroll_offset.x = (short)(elem.div.scroll_x.value*(float)(elem.children_bounds.w-elem.bounds.w));
|
||||
} else {
|
||||
elem.div.scroll_x.value = 0;
|
||||
}
|
||||
|
||||
// the active_div returns to the parent of the current one
|
||||
ctx.active_div = ctx.tree.parentof(ctx.active_div)!;
|
||||
Elem* parent = ctx.get_parent()!;
|
||||
ctx.div_scissor = parent.bounds.pad(parent.layout.content_offset).max({0,0,0,0});
|
||||
ctx.reset_scissor(elem.z_index)!;
|
||||
|
||||
update_parent_size(elem, parent);
|
||||
|
||||
return elem.id;
|
||||
}
|
||||
|
||||
<* @param[&inout] state *>
|
||||
macro Ctx.@popup(&ctx, bool* state,
|
||||
Point pos,
|
||||
Size width, Size height,
|
||||
LayoutDirection dir = ROW, Anchor anchor = TOP_LEFT,
|
||||
bool scroll_x = false, bool scroll_y = false,
|
||||
...; @body())
|
||||
{
|
||||
if (*state) {
|
||||
*state = ctx.popup_begin(pos, width, height, dir, anchor, scroll_x, scroll_y)!;
|
||||
@body();
|
||||
ctx.div_end()!;
|
||||
}
|
||||
}
|
||||
|
||||
macro bool? Ctx.popup_begin(&ctx, Point pos,
|
||||
Size width, Size height,
|
||||
LayoutDirection dir = ROW, Anchor anchor = TOP_LEFT,
|
||||
bool scroll_x = false, bool scroll_y = false,
|
||||
...
|
||||
)
|
||||
=> ctx.popup_begin_id(@compute_id($vasplat), pos, width, height, dir, anchor, scroll_x, scroll_y);
|
||||
fn bool? Ctx.popup_begin_id(&ctx,
|
||||
Id id,
|
||||
Point pos,
|
||||
Size width, Size height,
|
||||
LayoutDirection dir, Anchor anchor,
|
||||
bool scroll_x, bool scroll_y
|
||||
)
|
||||
{
|
||||
id = ctx.gen_id(id)!;
|
||||
|
||||
Elem* parent, elem;
|
||||
ctx.get_elem(id, ETYPE_DIV)!.unpack(&elem, &parent);
|
||||
ctx.active_div = elem.tree_idx;
|
||||
|
||||
Style* style = ctx.styles.get_style(@str_hash("popup"));
|
||||
|
||||
elem.div.scroll_x.enabled = scroll_x;
|
||||
elem.div.scroll_y.enabled = scroll_y;
|
||||
elem.z_index++;
|
||||
|
||||
// update layout with correct info
|
||||
elem.layout = {
|
||||
.w = width,
|
||||
.h = height,
|
||||
.dir = dir,
|
||||
.anchor = anchor,
|
||||
.content_offset = style.margin + style.border + style.padding,
|
||||
.absolute = true,
|
||||
.origin.x = pos.x - parent.bounds.x,
|
||||
.origin.y = pos.y - parent.bounds.y,
|
||||
};
|
||||
|
||||
// if the position is not the one expected then request a frame-skip to not flash appear in the
|
||||
// wrong position for one frame
|
||||
if (elem.bounds.position() != pos) {
|
||||
ctx.skip_frame = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
ctx.push_rect(elem.bounds.pad(style.margin), elem.z_index, style)!;
|
||||
|
||||
// update the ctx scissor, it HAS to be after drawing the background
|
||||
ctx.div_scissor = elem.bounds.pad(elem.layout.content_offset).max({0,0,0,0});
|
||||
ctx.push_scissor(ctx.div_scissor, elem.z_index)!;
|
||||
|
||||
//elem.events = ctx.get_elem_events(elem);
|
||||
|
||||
// check close condition, mouse release anywhere outside the div bounds
|
||||
if ((ctx.mouse_released() & BTN_ANY) && ctx.input.mouse.pos.outside(elem.bounds)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: check active
|
||||
// TODO: check resizeable
|
||||
return true;
|
||||
}
|
||||
56
src/widgets/separator.c3
Normal file
56
src/widgets/separator.c3
Normal file
@ -0,0 +1,56 @@
|
||||
module ugui;
|
||||
|
||||
|
||||
macro Ctx.separator(&ctx, Size width, Size height, ...)
|
||||
=> ctx.separator_id(@compute_id($vasplat), width, height);
|
||||
fn void? Ctx.separator_id(&ctx, Id id, Size width, Size height)
|
||||
{
|
||||
id = ctx.gen_id(id)!;
|
||||
Elem* parent, elem;
|
||||
ctx.get_elem(id, ETYPE_NONE)!.unpack(&elem, &parent);
|
||||
|
||||
elem.layout.w = width;
|
||||
elem.layout.h = height;
|
||||
|
||||
update_parent_size(elem, parent);
|
||||
}
|
||||
|
||||
|
||||
macro Ctx.hor_line(&ctx, ...)
|
||||
=> ctx.hor_line_id(@compute_id($vasplat));
|
||||
fn void? Ctx.hor_line_id(&ctx, Id id)
|
||||
{
|
||||
id = ctx.gen_id(id)!;
|
||||
Elem* parent, elem;
|
||||
ctx.get_elem(id, ETYPE_NONE)!.unpack(&elem, &parent);
|
||||
Style* style = ctx.styles.get_style(@str_hash("separator"));
|
||||
|
||||
elem.layout.w = @grow();
|
||||
elem.layout.h = @exact(style.size);
|
||||
elem.layout.content_offset = style.margin + style.border + style.padding;
|
||||
|
||||
update_parent_size(elem, parent);
|
||||
|
||||
Rect r = elem.bounds.pad(elem.layout.content_offset);
|
||||
ctx.push_rect(r, elem.z_index, style)!;
|
||||
}
|
||||
|
||||
|
||||
macro Ctx.ver_line(&ctx, ...)
|
||||
=> ctx.ver_line_id(@compute_id($vasplat));
|
||||
fn void? Ctx.ver_line_id(&ctx, Id id)
|
||||
{
|
||||
id = ctx.gen_id(id)!;
|
||||
Elem* parent, elem;
|
||||
ctx.get_elem(id, ETYPE_NONE)!.unpack(&elem, &parent);
|
||||
Style* style = ctx.styles.get_style(@str_hash("separator"));
|
||||
|
||||
elem.layout.w = @exact(style.size);
|
||||
elem.layout.h = @grow();
|
||||
elem.layout.content_offset = style.margin + style.border + style.padding;
|
||||
|
||||
update_parent_size(elem, parent);
|
||||
|
||||
Rect r = elem.bounds.pad(elem.layout.content_offset);
|
||||
ctx.push_rect(r, elem.z_index, style)!;
|
||||
}
|
||||
185
src/widgets/slider.c3
Normal file
185
src/widgets/slider.c3
Normal file
@ -0,0 +1,185 @@
|
||||
module ugui;
|
||||
|
||||
import std::io;
|
||||
import std::math;
|
||||
|
||||
// slider element
|
||||
struct ElemSlider {
|
||||
Rect handle;
|
||||
}
|
||||
|
||||
/* handle
|
||||
* +----+-----+---------------------+
|
||||
* | |#####| |
|
||||
* +----+-----+---------------------+
|
||||
*/
|
||||
macro Ctx.slider_hor(&ctx, Size w, Size h, float* value, float hpercent = 0.25, ...)
|
||||
=> ctx.slider_hor_id(@compute_id($vasplat), w, h, value, hpercent);
|
||||
<*
|
||||
@require value != null
|
||||
*>
|
||||
fn ElemEvents? Ctx.slider_hor_id(&ctx, Id id, Size w, Size h, float* value, float hpercent = 0.25)
|
||||
{
|
||||
id = ctx.gen_id(id)!;
|
||||
|
||||
Elem* parent, elem;
|
||||
ctx.get_elem(id, ETYPE_SLIDER)!.unpack(&elem, &parent);
|
||||
Style* style = ctx.styles.get_style(@str_hash("slider"));
|
||||
|
||||
elem.layout.w = w;
|
||||
elem.layout.h = h;
|
||||
elem.layout.content_offset = style.margin + style.border + style.padding;
|
||||
update_parent_size(elem, parent);
|
||||
|
||||
Rect bg_bounds = elem.bounds.pad(style.margin);
|
||||
Rect content_bounds = elem.bounds.pad(style.margin + style.border + style.padding);
|
||||
|
||||
// handle width
|
||||
short hw = (short)(content_bounds.w * hpercent);
|
||||
Rect handle = {
|
||||
.x = calc_slider(content_bounds.x, content_bounds.w-hw, *value),
|
||||
.y = content_bounds.y,
|
||||
.w = hw,
|
||||
.h = content_bounds.h,
|
||||
};
|
||||
elem.slider.handle = handle;
|
||||
|
||||
Point m = ctx.input.mouse.pos;
|
||||
//elem.events = ctx.get_elem_events(elem);
|
||||
|
||||
if (elem.events.has_focus && ctx.is_mouse_down(BTN_LEFT)) {
|
||||
*value = calc_value(content_bounds.x, m.x, content_bounds.w, hw);
|
||||
elem.slider.handle.x = calc_slider(content_bounds.x, content_bounds.w-hw, *value);
|
||||
elem.events.update = true;
|
||||
}
|
||||
|
||||
// Draw the slider background and handle
|
||||
Style s = *style;
|
||||
Rect padding = s.padding;
|
||||
s.padding = {};
|
||||
ctx.push_rect(bg_bounds, parent.z_index, &s)!;
|
||||
s.bg = s.primary;
|
||||
s.padding = padding;
|
||||
s.border = {};
|
||||
ctx.push_rect(elem.slider.handle, parent.z_index, &s)!;
|
||||
|
||||
return elem.events;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* +--+
|
||||
* | |
|
||||
* | |
|
||||
* +--+
|
||||
* |##| handle
|
||||
* |##|
|
||||
* +--+
|
||||
* | |
|
||||
* | |
|
||||
* +--+
|
||||
*/
|
||||
macro Ctx.slider_ver(&ctx, Size w, Size h, float* value, float hpercent = 0.25, ...)
|
||||
=> ctx.slider_ver_id(@compute_id($vasplat), w, h, value, hpercent);
|
||||
fn ElemEvents? Ctx.slider_ver_id(&ctx, Id id, Size w, Size h, float* value, float hpercent = 0.25)
|
||||
{
|
||||
id = ctx.gen_id(id)!;
|
||||
|
||||
Elem* parent, elem;
|
||||
ctx.get_elem(id, ETYPE_SLIDER)!.unpack(&elem, &parent);
|
||||
Style* style = ctx.styles.get_style(@str_hash("slider"));
|
||||
|
||||
elem.layout.w = w;
|
||||
elem.layout.h = h;
|
||||
elem.layout.content_offset = style.margin + style.border + style.padding;
|
||||
update_parent_size(elem, parent);
|
||||
|
||||
// 2. Layout
|
||||
Rect bg_bounds = elem.bounds.pad(style.margin);
|
||||
Rect content_bounds = elem.bounds.pad(style.margin + style.border + style.padding);
|
||||
|
||||
// handle height
|
||||
short hh = (short)(content_bounds.h * hpercent);
|
||||
Rect handle = {
|
||||
.x = content_bounds.x,
|
||||
.y = calc_slider(content_bounds.y, content_bounds.h-hh, *value),
|
||||
.w = content_bounds.w,
|
||||
.h = hh,
|
||||
};
|
||||
elem.slider.handle = handle;
|
||||
|
||||
Point m = ctx.input.mouse.pos;
|
||||
//elem.events = ctx.get_elem_events(elem);
|
||||
|
||||
if (elem.events.has_focus && ctx.is_mouse_down(BTN_LEFT)) {
|
||||
*value = calc_value(content_bounds.y, m.y, content_bounds.h, hh);
|
||||
elem.slider.handle.y = calc_slider(content_bounds.y, content_bounds.h-hh, *value);
|
||||
elem.events.update = true;
|
||||
}
|
||||
|
||||
// Draw the slider background and handle
|
||||
Style s = *style;
|
||||
Rect padding = s.padding;
|
||||
s.padding = {};
|
||||
ctx.push_rect(bg_bounds, parent.z_index, &s)!;
|
||||
s.bg = s.primary;
|
||||
s.padding = padding;
|
||||
s.border = {};
|
||||
ctx.push_rect(elem.slider.handle, parent.z_index, &s)!;
|
||||
|
||||
return elem.events;
|
||||
}
|
||||
|
||||
|
||||
fn void? Ctx.scrollbar(&ctx, Id id, float *value, float handle_percent, bool vertical = true)
|
||||
{
|
||||
id = ctx.gen_id(id)!;
|
||||
|
||||
Elem* parent, elem;
|
||||
ctx.get_elem(id, ETYPE_SLIDER)!.unpack(&elem, &parent);
|
||||
Style* style = ctx.styles.get_style(@str_hash("scrollbar"));
|
||||
|
||||
Rect pb = parent.bounds.pad(parent.layout.content_offset);
|
||||
if (vertical) {
|
||||
elem.layout.w = @exact(style.size);
|
||||
elem.layout.h = @grow();
|
||||
elem.layout.origin.x = pb.w - style.size;
|
||||
elem.layout.origin.y = 0;
|
||||
} else {
|
||||
elem.layout.w = @grow();
|
||||
elem.layout.h = @exact(style.size);
|
||||
elem.layout.origin.x = 0;
|
||||
elem.layout.origin.y = pb.h - style.size;
|
||||
}
|
||||
elem.layout.content_offset = style.margin + style.border + style.padding;
|
||||
elem.layout.absolute = true;
|
||||
update_parent_size(elem, parent);
|
||||
|
||||
Rect content_bounds = elem.bounds.pad(elem.layout.content_offset);
|
||||
|
||||
short o = vertical ? content_bounds.y : content_bounds.x;
|
||||
short m = vertical ? ctx.input.mouse.pos.y : ctx.input.mouse.pos.x;
|
||||
short s = vertical ? content_bounds.h : content_bounds.w;
|
||||
short h = (short)((float)s * handle_percent);
|
||||
if (elem.events.has_focus && ctx.is_mouse_down(BTN_LEFT)) {
|
||||
*value = calc_value(o, m, s, h);
|
||||
elem.events.update = true;
|
||||
}
|
||||
short handle_pos = calc_slider(o, s-h, *value);
|
||||
|
||||
elem.slider.handle = {
|
||||
.x = vertical ? content_bounds.x : handle_pos,
|
||||
.y = vertical ? handle_pos : content_bounds.y,
|
||||
.w = vertical ? content_bounds.w : h,
|
||||
.h = vertical ? h : content_bounds.h,
|
||||
};
|
||||
|
||||
|
||||
Rect bg_bounds = elem.bounds.pad(style.margin);
|
||||
ctx.push_rect(bg_bounds, parent.z_index, style)!;
|
||||
ctx.push_rect(elem.slider.handle, parent.z_index, &&(Style){.bg = style.primary, .radius = style.radius})!;
|
||||
}
|
||||
|
||||
macro short calc_slider(short off, short dim, float value) => (short)off + (short)(dim * value);
|
||||
macro float calc_value(short off, short mouse, short dim, short slider)
|
||||
=> math::clamp((float)(mouse-off-slider/2)/(float)(dim-slider), 0.0f, 1.0f);
|
||||
41
src/widgets/sprite.c3
Normal file
41
src/widgets/sprite.c3
Normal file
@ -0,0 +1,41 @@
|
||||
module ugui;
|
||||
|
||||
struct ElemSprite {
|
||||
Id id;
|
||||
}
|
||||
|
||||
macro Ctx.sprite(&ctx, String name, short size = 0, ...)
|
||||
=> ctx.sprite_id(@compute_id($vasplat), name, size);
|
||||
fn void? Ctx.sprite_id(&ctx, Id id, String name, short size = 0)
|
||||
{
|
||||
id = ctx.gen_id(id)!;
|
||||
Elem* parent, elem;
|
||||
ctx.get_elem(id, ETYPE_SPRITE)!.unpack(&elem, &parent);
|
||||
|
||||
Style* style = ctx.styles.get_style(@str_hash("sprite"));
|
||||
Sprite* sprite = ctx.sprite_atlas.get(name)!;
|
||||
elem.sprite.id = ctx.get_sprite_atlas_id(name);
|
||||
|
||||
// scale the sprite so that the biggest dimension becomes "size"
|
||||
short width = sprite.w;
|
||||
short height = sprite.h;
|
||||
if (size > 0) {
|
||||
if (sprite.w >= sprite.h) {
|
||||
height = (short)(size * (float)height/width);
|
||||
width = size;
|
||||
} else {
|
||||
width = (short)(size * (float)width/height);
|
||||
height = size;
|
||||
}
|
||||
}
|
||||
|
||||
elem.layout.w = elem.layout.children.w = @exact(width);
|
||||
elem.layout.h = elem.layout.children.h = @exact(height);
|
||||
elem.layout.content_offset = style.margin + style.border + style.padding;
|
||||
|
||||
update_parent_size(elem, parent);
|
||||
|
||||
Id tex_id = ctx.sprite_atlas.id;
|
||||
Rect content_bounds = elem.bounds.pad(elem.layout.content_offset);
|
||||
return ctx.push_sprite(content_bounds, sprite.uv(), tex_id, parent.z_index, type: sprite.type)!;
|
||||
}
|
||||
97
src/widgets/text.c3
Normal file
97
src/widgets/text.c3
Normal file
@ -0,0 +1,97 @@
|
||||
module ugui;
|
||||
|
||||
import std::io;
|
||||
|
||||
struct ElemText {
|
||||
Id hash;
|
||||
TextSize size;
|
||||
TextEdit* te;
|
||||
}
|
||||
|
||||
/* Layout some text without bounds.
|
||||
* There is a limitation where the current frame bounds are based on the last frame, this is usually
|
||||
* not a problem but it is in the situation where the text changes almost all frames.
|
||||
*/
|
||||
macro Ctx.text(&ctx, String text, ...)
|
||||
=> ctx.text_id(@compute_id($vasplat), text);
|
||||
fn void? Ctx.text_id(&ctx, Id id, String text)
|
||||
{
|
||||
id = ctx.gen_id(id)!;
|
||||
|
||||
Elem* parent, elem;
|
||||
ctx.get_elem(id, ETYPE_TEXT)!.unpack(&elem, &parent);
|
||||
Style* style = ctx.styles.get_style(@str_hash("text"));
|
||||
|
||||
Id text_hash = text.hash();
|
||||
if (elem.flags.is_new || elem.text.hash != text_hash) {
|
||||
elem.text.size = ctx.measure_string(text)!;
|
||||
}
|
||||
elem.text.hash = text_hash;
|
||||
|
||||
elem.layout.w = @fit(style.size);
|
||||
elem.layout.h = @fit(style.size);
|
||||
elem.layout.text = elem.text.size;
|
||||
elem.layout.content_offset = style.margin + style.border + style.padding;
|
||||
|
||||
update_parent_size(elem, parent);
|
||||
|
||||
ctx.layout_string(text, elem.bounds.pad(elem.layout.content_offset), TOP_LEFT, parent.z_index, style.fg)!;
|
||||
}
|
||||
|
||||
|
||||
macro Ctx.text_box(&ctx, Size w, Size h, TextEdit* te, Anchor text_alignment = TOP_LEFT, bool reflow = true, ...)
|
||||
=> ctx.text_box_id(@compute_id($vasplat), w, h, te, text_alignment, reflow);
|
||||
fn ElemEvents? Ctx.text_box_id(&ctx, Id id, Size w, Size h, TextEdit* te, Anchor text_alignment, bool reflow)
|
||||
{
|
||||
id = ctx.gen_id(id)!;
|
||||
|
||||
Elem* parent, elem;
|
||||
ctx.get_elem(id, ETYPE_TEXT)!.unpack(&elem, &parent);
|
||||
Style* style = ctx.styles.get_style(@str_hash("text-box"));
|
||||
|
||||
elem.text.te = te;
|
||||
|
||||
Id text_hash = te.to_string().hash();
|
||||
if (elem.flags.is_new || elem.text.hash != text_hash) {
|
||||
elem.text.size = ctx.measure_string(te.to_string())!;
|
||||
}
|
||||
elem.text.hash = text_hash;
|
||||
|
||||
elem.layout.w = w;
|
||||
elem.layout.h = h;
|
||||
elem.layout.text = elem.text.size;
|
||||
elem.layout.content_offset = style.margin + style.border + style.padding;
|
||||
|
||||
update_parent_size(elem, parent);
|
||||
|
||||
// check input and update the text
|
||||
//elem.events = ctx.get_elem_events(elem);
|
||||
|
||||
if (elem.events.text_input || elem.events.key_press) {
|
||||
ctx.text_edit(elem.text.te);
|
||||
}
|
||||
|
||||
Rect bg_bounds = elem.bounds.pad(style.margin);
|
||||
Rect text_bounds = elem.bounds.pad(elem.layout.content_offset);
|
||||
ctx.push_rect(bg_bounds, parent.z_index, style)!;
|
||||
String s = elem.text.te.to_string();
|
||||
if (te.sel_len) {
|
||||
usz start = te.sel_len > 0 ? te.cursor : te.cursor + te.sel_len;
|
||||
usz end = (te.sel_len > 0 ? te.cursor + te.sel_len : te.cursor) - 1;
|
||||
ctx.draw_string_selection(s, text_bounds, text_alignment, start, end, parent.z_index, style.accent, reflow)!;
|
||||
}
|
||||
ctx.layout_string(s, text_bounds, text_alignment, parent.z_index, style.fg, reflow)!;
|
||||
|
||||
// draw the cursor if the element has focus
|
||||
if (elem.events.has_focus) {
|
||||
if (elem.events.mouse_press || elem.events.mouse_hold) {
|
||||
usz cur = ctx.hit_test_string(s, text_bounds, text_alignment, ctx.input.mouse.pos, reflow)!;
|
||||
bool select = (elem.events.mouse_hold && !elem.events.mouse_press) || (ctx.get_mod() & KMOD_SHIFT);
|
||||
te.set_cursor(cur, select);
|
||||
}
|
||||
Rect cur = ctx.get_cursor_position(s, text_bounds, text_alignment, te.cursor, reflow)!;
|
||||
cur.w = 2;
|
||||
ctx.push_rect(cur, parent.z_index, &&(Style){.bg = style.fg})!;
|
||||
}
|
||||
return elem.events;
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
CFLAGS = -Wall -Wextra -Wpedantic -std=c11 -g
|
||||
LDFLAGS = -lSDL2 -lm
|
||||
|
||||
test: main.c ../ugui.c ../ugui.h ../def_style.h ../input.c
|
||||
gcc ${CFLAGS} -c ../ugui.c -o ugui.o
|
||||
gcc ${CFLAGS} -c ../input.c -o input.o
|
||||
gcc ${CFLAGS} -c main.c -o main.o
|
||||
gcc ${LDFLAGS} main.o ugui.o input.o -o test
|
||||
|
||||
clean:
|
||||
rm -f main.o ugui.o
|
||||
268
test/main.c
268
test/main.c
@ -1,268 +0,0 @@
|
||||
#define _POSIX_C_SOURCE 200809l
|
||||
|
||||
#include <stdio.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include "../ugui.h"
|
||||
|
||||
#define STB_RECT_PACK_IMPLEMENTATION
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#define STBTTF_IMPLEMENTATION
|
||||
#include "stbttf.h"
|
||||
|
||||
|
||||
SDL_Window *w;
|
||||
SDL_Renderer *r;
|
||||
ug_ctx_t *ctx;
|
||||
STBTTF_Font *font;
|
||||
|
||||
void cleanup(void);
|
||||
|
||||
int main(void)
|
||||
{
|
||||
SDL_DisplayMode dm;
|
||||
|
||||
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
|
||||
SDL_EnableScreenSaver();
|
||||
SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
|
||||
SDL_EventState(SDL_DROPTEXT, SDL_ENABLE);
|
||||
|
||||
SDL_GetDesktopDisplayMode(0, &dm);
|
||||
|
||||
#ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR /* Available since 2.0.8 */
|
||||
SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
|
||||
#endif
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 5)
|
||||
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
|
||||
#endif
|
||||
|
||||
w = SDL_CreateWindow("test",
|
||||
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
|
||||
dm.w*0.8, dm.h*0.8,
|
||||
SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI |
|
||||
SDL_WINDOW_OPENGL );
|
||||
|
||||
//SDL_Surface *s;
|
||||
//s = SDL_GetWindowSurface(w);
|
||||
r = SDL_CreateRenderer(w, -1, SDL_RENDERER_ACCELERATED);
|
||||
SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_BLEND);
|
||||
|
||||
ug_vec2_t size, dsize;
|
||||
SDL_GetWindowSize(w, &size.w, &size.h);
|
||||
SDL_GL_GetDrawableSize(w, &dsize.w, &dsize.h);
|
||||
float scale = 1.0;
|
||||
scale = ((float)(size.w+size.h)/2)/((float)(dsize.w+dsize.h)/2);
|
||||
|
||||
float dpi;
|
||||
int idx;
|
||||
idx = SDL_GetWindowDisplayIndex(w);
|
||||
SDL_GetDisplayDPI(idx, &dpi, NULL, NULL);
|
||||
|
||||
|
||||
ctx = ug_ctx_new();
|
||||
ug_ctx_set_displayinfo(ctx, scale, dpi);
|
||||
ug_ctx_set_drawableregion(ctx, dsize);
|
||||
|
||||
// open font
|
||||
font = STBTTF_OpenFont(r, "monospace.ttf", ctx->style_px->title.font_size.size.i);
|
||||
|
||||
// atexit(cleanup);
|
||||
|
||||
SDL_Event event;
|
||||
|
||||
char button_map[] = {
|
||||
[SDL_BUTTON_LEFT & 0xff] = UG_BTN_LEFT,
|
||||
[SDL_BUTTON_MIDDLE & 0xff] = UG_BTN_MIDDLE,
|
||||
[SDL_BUTTON_RIGHT & 0xff] = UG_BTN_RIGHT,
|
||||
[SDL_BUTTON_X1 & 0xff] = UG_BTN_4,
|
||||
[SDL_BUTTON_X2 & 0xff] = UG_BTN_5,
|
||||
};
|
||||
|
||||
do {
|
||||
SDL_WaitEvent(&event);
|
||||
|
||||
switch (event.type) {
|
||||
case SDL_WINDOWEVENT:
|
||||
switch (event.window.event) {
|
||||
case SDL_WINDOWEVENT_SHOWN:
|
||||
(SDL_Log("Window %d shown", event.window.windowID));
|
||||
break;
|
||||
case SDL_WINDOWEVENT_HIDDEN:
|
||||
(SDL_Log("Window %d hidden", event.window.windowID));
|
||||
break;
|
||||
case SDL_WINDOWEVENT_EXPOSED:
|
||||
(SDL_Log("Window %d exposed", event.window.windowID));
|
||||
break;
|
||||
case SDL_WINDOWEVENT_MOVED:
|
||||
(SDL_Log("Window %d moved to %d,%d",
|
||||
event.window.windowID, event.window.data1,
|
||||
event.window.data2));
|
||||
break;
|
||||
case SDL_WINDOWEVENT_RESIZED:
|
||||
(SDL_Log("Window %d resized to %dx%d",
|
||||
event.window.windowID, event.window.data1,
|
||||
event.window.data2));
|
||||
break;
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
(SDL_Log("Window %d size changed to %dx%d",
|
||||
event.window.windowID, event.window.data1,
|
||||
event.window.data2));
|
||||
|
||||
size.w = event.window.data1;
|
||||
size.h = event.window.data2;
|
||||
// surface is invalidated every time the window
|
||||
// is resized
|
||||
//s = SDL_GetWindowSurface(w);
|
||||
ug_ctx_set_drawableregion(ctx, size);
|
||||
|
||||
break;
|
||||
case SDL_WINDOWEVENT_MINIMIZED:
|
||||
(SDL_Log("Window %d minimized", event.window.windowID));
|
||||
break;
|
||||
case SDL_WINDOWEVENT_MAXIMIZED:
|
||||
(SDL_Log("Window %d maximized", event.window.windowID));
|
||||
break;
|
||||
case SDL_WINDOWEVENT_RESTORED:
|
||||
(SDL_Log("Window %d restored", event.window.windowID));
|
||||
break;
|
||||
case SDL_WINDOWEVENT_ENTER:
|
||||
(SDL_Log("Mouse entered window %d",
|
||||
event.window.windowID));
|
||||
break;
|
||||
case SDL_WINDOWEVENT_LEAVE:
|
||||
(SDL_Log("Mouse left window %d", event.window.windowID));
|
||||
break;
|
||||
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
||||
(SDL_Log("Window %d gained keyboard focus",
|
||||
event.window.windowID));
|
||||
break;
|
||||
case SDL_WINDOWEVENT_FOCUS_LOST:
|
||||
(SDL_Log("Window %d lost keyboard focus",
|
||||
event.window.windowID));
|
||||
break;
|
||||
case SDL_WINDOWEVENT_CLOSE:
|
||||
(SDL_Log("Window %d closed", event.window.windowID));
|
||||
break;
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 5)
|
||||
case SDL_WINDOWEVENT_TAKE_FOCUS:
|
||||
(SDL_Log("Window %d is offered a focus", event.window.windowID));
|
||||
break;
|
||||
case SDL_WINDOWEVENT_HIT_TEST:
|
||||
(SDL_Log("Window %d has a special hit test", event.window.windowID));
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
(SDL_Log("Window %d got unknown event %d",
|
||||
event.window.windowID, event.window.event));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SDL_QUIT:
|
||||
(printf("Quitting\n"));
|
||||
break;
|
||||
case SDL_MOUSEMOTION:
|
||||
|
||||
ug_input_mousemove(ctx, event.motion.x, event.motion.y);
|
||||
|
||||
break;
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
ug_input_mousemove(ctx, event.button.x, event.button.y);
|
||||
ug_input_mousedown(ctx, button_map[event.button.button & 0xff]);
|
||||
|
||||
break;
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
|
||||
ug_input_mousemove(ctx, event.button.x, event.button.y);
|
||||
ug_input_mouseup(ctx, button_map[event.button.button & 0xff]);
|
||||
|
||||
break;
|
||||
default:
|
||||
(printf("Unknown event: %d\n", event.type));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
ug_frame_begin(ctx);
|
||||
|
||||
//if (ctx->frame < 5000) {
|
||||
// ug_container_menu_bar(ctx, "Menu fichissimo", (ug_size_t)SIZE_PX(24));
|
||||
//} else if (ctx->frame == 5000) {
|
||||
// ug_container_remove(ctx, "Menu fichissimo");
|
||||
//}
|
||||
|
||||
ug_container_floating(ctx, "stupid name", (ug_div_t){.x=SIZE_PX(0), .y=SIZE_PX(0), .w=SIZE_PX(100), .h=SIZE_MM(75.0)});
|
||||
ug_element_button(ctx, "float", "X", (ug_div_t){SQUARE(SIZE_MM(5))});
|
||||
//ug_container_floating(ctx, "floating windoooooooow", (ug_div_t){.x=SIZE_PX(100), .y=SIZE_PX(0), .w=SIZE_PX(100), .h=SIZE_MM(75.0)});
|
||||
|
||||
//ug_container_sidebar(ctx, "Right Sidebar", (ug_size_t)SIZE_PX(300), UG_SIDE_RIGHT);
|
||||
//ug_container_sidebar(ctx, "Left Sidebar", (ug_size_t)SIZE_PX(200), UG_SIDE_LEFT);
|
||||
//ug_container_sidebar(ctx, "Bottom Sidebar", (ug_size_t)SIZE_MM(10), UG_SIDE_BOTTOM);
|
||||
ug_container_sidebar(ctx, "Top Sidebar", (ug_size_t)SIZE_MM(40), UG_SIDE_TOP);
|
||||
|
||||
// ug_container_popup(ctx, "Annoying popup", (ug_div_t){.x=SIZE_MM(150), .y=SIZE_MM(150), .w=SIZE_PX(100), .h=SIZE_MM(75.0)});
|
||||
|
||||
//ug_container_body(ctx, "Main Body");
|
||||
//if (ug_container_body(ctx, "Other Body"))
|
||||
// printf("No space!\n");
|
||||
|
||||
ug_layout_row(ctx);
|
||||
|
||||
ug_layout_column(ctx);
|
||||
ug_element_button(ctx, "button 1", "hey", (ug_div_t){SQUARE(SIZE_MM(10))});
|
||||
//ug_element_button(ctx, "button 2", "lol", (ug_div_t){SQUARE(SIZE_MM(10))});
|
||||
//ug_layout_next_row(ctx);
|
||||
//ug_element_button(ctx, "button 3", "L", (ug_div_t){SQUARE(SIZE_MM(10))});
|
||||
//ug_element_button(ctx, "button 4", "69", (ug_div_t){SQUARE(SIZE_MM(10))});
|
||||
ug_layout_next_column(ctx);
|
||||
ug_element_button(ctx, "button 5", "lmao", (ug_div_t){SQUARE(SIZE_MM(10))});
|
||||
//ug_element_textbtn(ctx, "text button 1", "foo", (ug_div_t){.w = 0, .h = SIZE_PX(30)});
|
||||
//ug_element_button(ctx, "button 6", "", (ug_div_t){SQUARE(SIZE_MM(10)),.x=SIZE_PX(-10)});
|
||||
|
||||
ug_container_body(ctx, "fill body");
|
||||
|
||||
ug_frame_end(ctx);
|
||||
|
||||
// fill background
|
||||
// solid purple makes it easy to identify, same color as hl missing texture
|
||||
SDL_SetRenderDrawColor(r, 0xff, 0, 0xdc, 0xff);
|
||||
SDL_RenderClear(r);
|
||||
for (ug_cmd_t *cmd = NULL; (cmd = ug_cmd_next(ctx));) {
|
||||
switch (cmd->type) {
|
||||
case UG_CMD_RECT:
|
||||
{
|
||||
ug_color_t col = cmd->rect.color;
|
||||
SDL_Rect sr = {
|
||||
.x = cmd->rect.x,
|
||||
.y = cmd->rect.y,
|
||||
.w = cmd->rect.w,
|
||||
.h = cmd->rect.h,
|
||||
};
|
||||
SDL_SetRenderDrawColor(r, col.r, col.g, col.b, col.a);
|
||||
SDL_RenderFillRect(r, &sr);
|
||||
}
|
||||
break;
|
||||
case UG_CMD_TEXT:
|
||||
SDL_SetRenderDrawColor(r, cmd->text.color.r, cmd->text.color.g, cmd->text.color.b, cmd->text.color.a);
|
||||
STBTTF_RenderText(r, font, cmd->text.x, cmd->text.y, cmd->text.str);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
SDL_RenderPresent(r);
|
||||
|
||||
} while (event.type != SDL_QUIT);
|
||||
|
||||
cleanup();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cleanup(void)
|
||||
{
|
||||
ug_ctx_free(ctx);
|
||||
STBTTF_CloseFont(font);
|
||||
SDL_DestroyRenderer(r);
|
||||
SDL_DestroyWindow(w);
|
||||
SDL_Quit();
|
||||
}
|
||||
Binary file not shown.
@ -1,623 +0,0 @@
|
||||
// stb_rect_pack.h - v1.01 - public domain - rectangle packing
|
||||
// Sean Barrett 2014
|
||||
//
|
||||
// Useful for e.g. packing rectangular textures into an atlas.
|
||||
// Does not do rotation.
|
||||
//
|
||||
// Before #including,
|
||||
//
|
||||
// #define STB_RECT_PACK_IMPLEMENTATION
|
||||
//
|
||||
// in the file that you want to have the implementation.
|
||||
//
|
||||
// Not necessarily the awesomest packing method, but better than
|
||||
// the totally naive one in stb_truetype (which is primarily what
|
||||
// this is meant to replace).
|
||||
//
|
||||
// Has only had a few tests run, may have issues.
|
||||
//
|
||||
// More docs to come.
|
||||
//
|
||||
// No memory allocations; uses qsort() and assert() from stdlib.
|
||||
// Can override those by defining STBRP_SORT and STBRP_ASSERT.
|
||||
//
|
||||
// This library currently uses the Skyline Bottom-Left algorithm.
|
||||
//
|
||||
// Please note: better rectangle packers are welcome! Please
|
||||
// implement them to the same API, but with a different init
|
||||
// function.
|
||||
//
|
||||
// Credits
|
||||
//
|
||||
// Library
|
||||
// Sean Barrett
|
||||
// Minor features
|
||||
// Martins Mozeiko
|
||||
// github:IntellectualKitty
|
||||
//
|
||||
// Bugfixes / warning fixes
|
||||
// Jeremy Jaussaud
|
||||
// Fabian Giesen
|
||||
//
|
||||
// Version history:
|
||||
//
|
||||
// 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section
|
||||
// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles
|
||||
// 0.99 (2019-02-07) warning fixes
|
||||
// 0.11 (2017-03-03) return packing success/fail result
|
||||
// 0.10 (2016-10-25) remove cast-away-const to avoid warnings
|
||||
// 0.09 (2016-08-27) fix compiler warnings
|
||||
// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0)
|
||||
// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0)
|
||||
// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort
|
||||
// 0.05: added STBRP_ASSERT to allow replacing assert
|
||||
// 0.04: fixed minor bug in STBRP_LARGE_RECTS support
|
||||
// 0.01: initial release
|
||||
//
|
||||
// LICENSE
|
||||
//
|
||||
// See end of file for license information.
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// INCLUDE SECTION
|
||||
//
|
||||
|
||||
#ifndef STB_INCLUDE_STB_RECT_PACK_H
|
||||
#define STB_INCLUDE_STB_RECT_PACK_H
|
||||
|
||||
#define STB_RECT_PACK_VERSION 1
|
||||
|
||||
#ifdef STBRP_STATIC
|
||||
#define STBRP_DEF static
|
||||
#else
|
||||
#define STBRP_DEF extern
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct stbrp_context stbrp_context;
|
||||
typedef struct stbrp_node stbrp_node;
|
||||
typedef struct stbrp_rect stbrp_rect;
|
||||
|
||||
typedef int stbrp_coord;
|
||||
|
||||
#define STBRP__MAXVAL 0x7fffffff
|
||||
// Mostly for internal use, but this is the maximum supported coordinate value.
|
||||
|
||||
STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects);
|
||||
// Assign packed locations to rectangles. The rectangles are of type
|
||||
// 'stbrp_rect' defined below, stored in the array 'rects', and there
|
||||
// are 'num_rects' many of them.
|
||||
//
|
||||
// Rectangles which are successfully packed have the 'was_packed' flag
|
||||
// set to a non-zero value and 'x' and 'y' store the minimum location
|
||||
// on each axis (i.e. bottom-left in cartesian coordinates, top-left
|
||||
// if you imagine y increasing downwards). Rectangles which do not fit
|
||||
// have the 'was_packed' flag set to 0.
|
||||
//
|
||||
// You should not try to access the 'rects' array from another thread
|
||||
// while this function is running, as the function temporarily reorders
|
||||
// the array while it executes.
|
||||
//
|
||||
// To pack into another rectangle, you need to call stbrp_init_target
|
||||
// again. To continue packing into the same rectangle, you can call
|
||||
// this function again. Calling this multiple times with multiple rect
|
||||
// arrays will probably produce worse packing results than calling it
|
||||
// a single time with the full rectangle array, but the option is
|
||||
// available.
|
||||
//
|
||||
// The function returns 1 if all of the rectangles were successfully
|
||||
// packed and 0 otherwise.
|
||||
|
||||
struct stbrp_rect
|
||||
{
|
||||
// reserved for your use:
|
||||
int id;
|
||||
|
||||
// input:
|
||||
stbrp_coord w, h;
|
||||
|
||||
// output:
|
||||
stbrp_coord x, y;
|
||||
int was_packed; // non-zero if valid packing
|
||||
|
||||
}; // 16 bytes, nominally
|
||||
|
||||
|
||||
STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes);
|
||||
// Initialize a rectangle packer to:
|
||||
// pack a rectangle that is 'width' by 'height' in dimensions
|
||||
// using temporary storage provided by the array 'nodes', which is 'num_nodes' long
|
||||
//
|
||||
// You must call this function every time you start packing into a new target.
|
||||
//
|
||||
// There is no "shutdown" function. The 'nodes' memory must stay valid for
|
||||
// the following stbrp_pack_rects() call (or calls), but can be freed after
|
||||
// the call (or calls) finish.
|
||||
//
|
||||
// Note: to guarantee best results, either:
|
||||
// 1. make sure 'num_nodes' >= 'width'
|
||||
// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1'
|
||||
//
|
||||
// If you don't do either of the above things, widths will be quantized to multiples
|
||||
// of small integers to guarantee the algorithm doesn't run out of temporary storage.
|
||||
//
|
||||
// If you do #2, then the non-quantized algorithm will be used, but the algorithm
|
||||
// may run out of temporary storage and be unable to pack some rectangles.
|
||||
|
||||
STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem);
|
||||
// Optionally call this function after init but before doing any packing to
|
||||
// change the handling of the out-of-temp-memory scenario, described above.
|
||||
// If you call init again, this will be reset to the default (false).
|
||||
|
||||
|
||||
STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic);
|
||||
// Optionally select which packing heuristic the library should use. Different
|
||||
// heuristics will produce better/worse results for different data sets.
|
||||
// If you call init again, this will be reset to the default.
|
||||
|
||||
enum
|
||||
{
|
||||
STBRP_HEURISTIC_Skyline_default=0,
|
||||
STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default,
|
||||
STBRP_HEURISTIC_Skyline_BF_sortHeight
|
||||
};
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// the details of the following structures don't matter to you, but they must
|
||||
// be visible so you can handle the memory allocations for them
|
||||
|
||||
struct stbrp_node
|
||||
{
|
||||
stbrp_coord x,y;
|
||||
stbrp_node *next;
|
||||
};
|
||||
|
||||
struct stbrp_context
|
||||
{
|
||||
int width;
|
||||
int height;
|
||||
int align;
|
||||
int init_mode;
|
||||
int heuristic;
|
||||
int num_nodes;
|
||||
stbrp_node *active_head;
|
||||
stbrp_node *free_head;
|
||||
stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2'
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPLEMENTATION SECTION
|
||||
//
|
||||
|
||||
#ifdef STB_RECT_PACK_IMPLEMENTATION
|
||||
#ifndef STBRP_SORT
|
||||
#include <stdlib.h>
|
||||
#define STBRP_SORT qsort
|
||||
#endif
|
||||
|
||||
#ifndef STBRP_ASSERT
|
||||
#include <assert.h>
|
||||
#define STBRP_ASSERT assert
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define STBRP__NOTUSED(v) (void)(v)
|
||||
#define STBRP__CDECL __cdecl
|
||||
#else
|
||||
#define STBRP__NOTUSED(v) (void)sizeof(v)
|
||||
#define STBRP__CDECL
|
||||
#endif
|
||||
|
||||
enum
|
||||
{
|
||||
STBRP__INIT_skyline = 1
|
||||
};
|
||||
|
||||
STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic)
|
||||
{
|
||||
switch (context->init_mode) {
|
||||
case STBRP__INIT_skyline:
|
||||
STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight);
|
||||
context->heuristic = heuristic;
|
||||
break;
|
||||
default:
|
||||
STBRP_ASSERT(0);
|
||||
}
|
||||
}
|
||||
|
||||
STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem)
|
||||
{
|
||||
if (allow_out_of_mem)
|
||||
// if it's ok to run out of memory, then don't bother aligning them;
|
||||
// this gives better packing, but may fail due to OOM (even though
|
||||
// the rectangles easily fit). @TODO a smarter approach would be to only
|
||||
// quantize once we've hit OOM, then we could get rid of this parameter.
|
||||
context->align = 1;
|
||||
else {
|
||||
// if it's not ok to run out of memory, then quantize the widths
|
||||
// so that num_nodes is always enough nodes.
|
||||
//
|
||||
// I.e. num_nodes * align >= width
|
||||
// align >= width / num_nodes
|
||||
// align = ceil(width/num_nodes)
|
||||
|
||||
context->align = (context->width + context->num_nodes-1) / context->num_nodes;
|
||||
}
|
||||
}
|
||||
|
||||
STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i=0; i < num_nodes-1; ++i)
|
||||
nodes[i].next = &nodes[i+1];
|
||||
nodes[i].next = NULL;
|
||||
context->init_mode = STBRP__INIT_skyline;
|
||||
context->heuristic = STBRP_HEURISTIC_Skyline_default;
|
||||
context->free_head = &nodes[0];
|
||||
context->active_head = &context->extra[0];
|
||||
context->width = width;
|
||||
context->height = height;
|
||||
context->num_nodes = num_nodes;
|
||||
stbrp_setup_allow_out_of_mem(context, 0);
|
||||
|
||||
// node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly)
|
||||
context->extra[0].x = 0;
|
||||
context->extra[0].y = 0;
|
||||
context->extra[0].next = &context->extra[1];
|
||||
context->extra[1].x = (stbrp_coord) width;
|
||||
context->extra[1].y = (1<<30);
|
||||
context->extra[1].next = NULL;
|
||||
}
|
||||
|
||||
// find minimum y position if it starts at x1
|
||||
static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste)
|
||||
{
|
||||
stbrp_node *node = first;
|
||||
int x1 = x0 + width;
|
||||
int min_y, visited_width, waste_area;
|
||||
|
||||
STBRP__NOTUSED(c);
|
||||
|
||||
STBRP_ASSERT(first->x <= x0);
|
||||
|
||||
#if 0
|
||||
// skip in case we're past the node
|
||||
while (node->next->x <= x0)
|
||||
++node;
|
||||
#else
|
||||
STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency
|
||||
#endif
|
||||
|
||||
STBRP_ASSERT(node->x <= x0);
|
||||
|
||||
min_y = 0;
|
||||
waste_area = 0;
|
||||
visited_width = 0;
|
||||
while (node->x < x1) {
|
||||
if (node->y > min_y) {
|
||||
// raise min_y higher.
|
||||
// we've accounted for all waste up to min_y,
|
||||
// but we'll now add more waste for everything we've visted
|
||||
waste_area += visited_width * (node->y - min_y);
|
||||
min_y = node->y;
|
||||
// the first time through, visited_width might be reduced
|
||||
if (node->x < x0)
|
||||
visited_width += node->next->x - x0;
|
||||
else
|
||||
visited_width += node->next->x - node->x;
|
||||
} else {
|
||||
// add waste area
|
||||
int under_width = node->next->x - node->x;
|
||||
if (under_width + visited_width > width)
|
||||
under_width = width - visited_width;
|
||||
waste_area += under_width * (min_y - node->y);
|
||||
visited_width += under_width;
|
||||
}
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
*pwaste = waste_area;
|
||||
return min_y;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int x,y;
|
||||
stbrp_node **prev_link;
|
||||
} stbrp__findresult;
|
||||
|
||||
static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height)
|
||||
{
|
||||
int best_waste = (1<<30), best_x, best_y = (1 << 30);
|
||||
stbrp__findresult fr;
|
||||
stbrp_node **prev, *node, *tail, **best = NULL;
|
||||
|
||||
// align to multiple of c->align
|
||||
width = (width + c->align - 1);
|
||||
width -= width % c->align;
|
||||
STBRP_ASSERT(width % c->align == 0);
|
||||
|
||||
// if it can't possibly fit, bail immediately
|
||||
if (width > c->width || height > c->height) {
|
||||
fr.prev_link = NULL;
|
||||
fr.x = fr.y = 0;
|
||||
return fr;
|
||||
}
|
||||
|
||||
node = c->active_head;
|
||||
prev = &c->active_head;
|
||||
while (node->x + width <= c->width) {
|
||||
int y,waste;
|
||||
y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste);
|
||||
if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL
|
||||
// bottom left
|
||||
if (y < best_y) {
|
||||
best_y = y;
|
||||
best = prev;
|
||||
}
|
||||
} else {
|
||||
// best-fit
|
||||
if (y + height <= c->height) {
|
||||
// can only use it if it first vertically
|
||||
if (y < best_y || (y == best_y && waste < best_waste)) {
|
||||
best_y = y;
|
||||
best_waste = waste;
|
||||
best = prev;
|
||||
}
|
||||
}
|
||||
}
|
||||
prev = &node->next;
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
best_x = (best == NULL) ? 0 : (*best)->x;
|
||||
|
||||
// if doing best-fit (BF), we also have to try aligning right edge to each node position
|
||||
//
|
||||
// e.g, if fitting
|
||||
//
|
||||
// ____________________
|
||||
// |____________________|
|
||||
//
|
||||
// into
|
||||
//
|
||||
// | |
|
||||
// | ____________|
|
||||
// |____________|
|
||||
//
|
||||
// then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned
|
||||
//
|
||||
// This makes BF take about 2x the time
|
||||
|
||||
if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) {
|
||||
tail = c->active_head;
|
||||
node = c->active_head;
|
||||
prev = &c->active_head;
|
||||
// find first node that's admissible
|
||||
while (tail->x < width)
|
||||
tail = tail->next;
|
||||
while (tail) {
|
||||
int xpos = tail->x - width;
|
||||
int y,waste;
|
||||
STBRP_ASSERT(xpos >= 0);
|
||||
// find the left position that matches this
|
||||
while (node->next->x <= xpos) {
|
||||
prev = &node->next;
|
||||
node = node->next;
|
||||
}
|
||||
STBRP_ASSERT(node->next->x > xpos && node->x <= xpos);
|
||||
y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste);
|
||||
if (y + height <= c->height) {
|
||||
if (y <= best_y) {
|
||||
if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) {
|
||||
best_x = xpos;
|
||||
STBRP_ASSERT(y <= best_y);
|
||||
best_y = y;
|
||||
best_waste = waste;
|
||||
best = prev;
|
||||
}
|
||||
}
|
||||
}
|
||||
tail = tail->next;
|
||||
}
|
||||
}
|
||||
|
||||
fr.prev_link = best;
|
||||
fr.x = best_x;
|
||||
fr.y = best_y;
|
||||
return fr;
|
||||
}
|
||||
|
||||
static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height)
|
||||
{
|
||||
// find best position according to heuristic
|
||||
stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height);
|
||||
stbrp_node *node, *cur;
|
||||
|
||||
// bail if:
|
||||
// 1. it failed
|
||||
// 2. the best node doesn't fit (we don't always check this)
|
||||
// 3. we're out of memory
|
||||
if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) {
|
||||
res.prev_link = NULL;
|
||||
return res;
|
||||
}
|
||||
|
||||
// on success, create new node
|
||||
node = context->free_head;
|
||||
node->x = (stbrp_coord) res.x;
|
||||
node->y = (stbrp_coord) (res.y + height);
|
||||
|
||||
context->free_head = node->next;
|
||||
|
||||
// insert the new node into the right starting point, and
|
||||
// let 'cur' point to the remaining nodes needing to be
|
||||
// stiched back in
|
||||
|
||||
cur = *res.prev_link;
|
||||
if (cur->x < res.x) {
|
||||
// preserve the existing one, so start testing with the next one
|
||||
stbrp_node *next = cur->next;
|
||||
cur->next = node;
|
||||
cur = next;
|
||||
} else {
|
||||
*res.prev_link = node;
|
||||
}
|
||||
|
||||
// from here, traverse cur and free the nodes, until we get to one
|
||||
// that shouldn't be freed
|
||||
while (cur->next && cur->next->x <= res.x + width) {
|
||||
stbrp_node *next = cur->next;
|
||||
// move the current node to the free list
|
||||
cur->next = context->free_head;
|
||||
context->free_head = cur;
|
||||
cur = next;
|
||||
}
|
||||
|
||||
// stitch the list back in
|
||||
node->next = cur;
|
||||
|
||||
if (cur->x < res.x + width)
|
||||
cur->x = (stbrp_coord) (res.x + width);
|
||||
|
||||
#ifdef _DEBUG
|
||||
cur = context->active_head;
|
||||
while (cur->x < context->width) {
|
||||
STBRP_ASSERT(cur->x < cur->next->x);
|
||||
cur = cur->next;
|
||||
}
|
||||
STBRP_ASSERT(cur->next == NULL);
|
||||
|
||||
{
|
||||
int count=0;
|
||||
cur = context->active_head;
|
||||
while (cur) {
|
||||
cur = cur->next;
|
||||
++count;
|
||||
}
|
||||
cur = context->free_head;
|
||||
while (cur) {
|
||||
cur = cur->next;
|
||||
++count;
|
||||
}
|
||||
STBRP_ASSERT(count == context->num_nodes+2);
|
||||
}
|
||||
#endif
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int STBRP__CDECL rect_height_compare(const void *a, const void *b)
|
||||
{
|
||||
const stbrp_rect *p = (const stbrp_rect *) a;
|
||||
const stbrp_rect *q = (const stbrp_rect *) b;
|
||||
if (p->h > q->h)
|
||||
return -1;
|
||||
if (p->h < q->h)
|
||||
return 1;
|
||||
return (p->w > q->w) ? -1 : (p->w < q->w);
|
||||
}
|
||||
|
||||
static int STBRP__CDECL rect_original_order(const void *a, const void *b)
|
||||
{
|
||||
const stbrp_rect *p = (const stbrp_rect *) a;
|
||||
const stbrp_rect *q = (const stbrp_rect *) b;
|
||||
return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed);
|
||||
}
|
||||
|
||||
STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects)
|
||||
{
|
||||
int i, all_rects_packed = 1;
|
||||
|
||||
// we use the 'was_packed' field internally to allow sorting/unsorting
|
||||
for (i=0; i < num_rects; ++i) {
|
||||
rects[i].was_packed = i;
|
||||
}
|
||||
|
||||
// sort according to heuristic
|
||||
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare);
|
||||
|
||||
for (i=0; i < num_rects; ++i) {
|
||||
if (rects[i].w == 0 || rects[i].h == 0) {
|
||||
rects[i].x = rects[i].y = 0; // empty rect needs no space
|
||||
} else {
|
||||
stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h);
|
||||
if (fr.prev_link) {
|
||||
rects[i].x = (stbrp_coord) fr.x;
|
||||
rects[i].y = (stbrp_coord) fr.y;
|
||||
} else {
|
||||
rects[i].x = rects[i].y = STBRP__MAXVAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unsort
|
||||
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order);
|
||||
|
||||
// set was_packed flags and all_rects_packed status
|
||||
for (i=0; i < num_rects; ++i) {
|
||||
rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL);
|
||||
if (!rects[i].was_packed)
|
||||
all_rects_packed = 0;
|
||||
}
|
||||
|
||||
// return the all_rects_packed status
|
||||
return all_rects_packed;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------------
|
||||
This software is available under 2 licenses -- choose whichever you prefer.
|
||||
------------------------------------------------------------------------------
|
||||
ALTERNATIVE A - MIT License
|
||||
Copyright (c) 2017 Sean Barrett
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
------------------------------------------------------------------------------
|
||||
ALTERNATIVE B - Public Domain (www.unlicense.org)
|
||||
This is free and unencumbered software released into the public domain.
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
|
||||
software, either in source code form or as a compiled binary, for any purpose,
|
||||
commercial or non-commercial, and by any means.
|
||||
In jurisdictions that recognize copyright laws, the author or authors of this
|
||||
software dedicate any and all copyright interest in the software to the public
|
||||
domain. We make this dedication for the benefit of the public at large and to
|
||||
the detriment of our heirs and successors. We intend this dedication to be an
|
||||
overt act of relinquishment in perpetuity of all present and future rights to
|
||||
this software under copyright law.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
------------------------------------------------------------------------------
|
||||
*/
|
||||
5077
test/stb_truetype.h
5077
test/stb_truetype.h
File diff suppressed because it is too large
Load Diff
212
test/stbttf.h
212
test/stbttf.h
@ -1,212 +0,0 @@
|
||||
#ifndef __STBTTF_H__
|
||||
#define __STBTTF_H__
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include "stb_rect_pack.h"
|
||||
#include "stb_truetype.h"
|
||||
|
||||
/* STBTTF: A quick and dirty SDL2 text renderer based on stb_truetype and stdb_rect_pack.
|
||||
* Benoit Favre 2019
|
||||
*
|
||||
* This header-only addon to the stb_truetype library allows to draw text with SDL2 from
|
||||
* TTF fonts with a similar API to SDL_TTF without the bloat.
|
||||
* The renderer is however limited by the integral positioning of SDL blit functions.
|
||||
* It also does not parse utf8 text and only prints ASCII characters.
|
||||
*
|
||||
* This code is public domain.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
stbtt_fontinfo* info;
|
||||
stbtt_packedchar* chars;
|
||||
SDL_Texture* atlas;
|
||||
int texture_size;
|
||||
float size;
|
||||
float scale;
|
||||
int ascent;
|
||||
int baseline;
|
||||
} STBTTF_Font;
|
||||
|
||||
/* Release the memory and textures associated with a font */
|
||||
void STBTTF_CloseFont(STBTTF_Font* font);
|
||||
|
||||
/* Open a TTF font given a SDL abstract IO handler, for a given renderer and a given font size.
|
||||
* Returns NULL on failure. The font must be deallocated with STBTTF_CloseFont when not used anymore.
|
||||
* This function creates a texture atlas with prerendered ASCII characters (32-128).
|
||||
*/
|
||||
STBTTF_Font* STBTTF_OpenFontRW(SDL_Renderer* renderer, SDL_RWops* rw, float size);
|
||||
|
||||
/* Open a TTF font given a filename, for a given renderer and a given font size.
|
||||
* Convinience function which calls STBTTF_OpenFontRW.
|
||||
*/
|
||||
STBTTF_Font* STBTTF_OpenFont(SDL_Renderer* renderer, const char* filename, float size);
|
||||
|
||||
/* Draw some text using the renderer draw color at location (x, y).
|
||||
* Characters are copied from the texture atlas using the renderer SDL_RenderCopy function.
|
||||
* Since that function only supports integral coordinates, the result is not great.
|
||||
* Only ASCII characters (32 <= c < 128) are supported. Anything outside this range is ignored.
|
||||
*/
|
||||
void STBTTF_RenderText(SDL_Renderer* renderer, STBTTF_Font* font, float x, float y, const char *text);
|
||||
|
||||
/* Return the length in pixels of a text.
|
||||
* You can get the height of a line by using font->baseline.
|
||||
*/
|
||||
float STBTTF_MeasureText(STBTTF_Font* font, const char *text);
|
||||
|
||||
#ifdef STBTTF_IMPLEMENTATION
|
||||
|
||||
void STBTTF_CloseFont(STBTTF_Font* font) {
|
||||
if(font->atlas) SDL_DestroyTexture(font->atlas);
|
||||
if(font->info) free(font->info);
|
||||
if(font->chars) free(font->chars);
|
||||
free(font);
|
||||
}
|
||||
|
||||
STBTTF_Font* STBTTF_OpenFontRW(SDL_Renderer* renderer, SDL_RWops* rw, float size) {
|
||||
Sint64 file_size = SDL_RWsize(rw);
|
||||
unsigned char* buffer = malloc(file_size);
|
||||
if(SDL_RWread(rw, buffer, file_size, 1) != 1) return NULL;
|
||||
SDL_RWclose(rw);
|
||||
|
||||
STBTTF_Font* font = calloc(sizeof(STBTTF_Font), 1);
|
||||
font->info = malloc(sizeof(stbtt_fontinfo));
|
||||
font->chars = malloc(sizeof(stbtt_packedchar) * 96);
|
||||
|
||||
if(stbtt_InitFont(font->info, buffer, 0) == 0) {
|
||||
free(buffer);
|
||||
STBTTF_CloseFont(font);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// fill bitmap atlas with packed characters
|
||||
unsigned char* bitmap = NULL;
|
||||
font->texture_size = 32;
|
||||
while(1) {
|
||||
bitmap = malloc(font->texture_size * font->texture_size);
|
||||
stbtt_pack_context pack_context;
|
||||
stbtt_PackBegin(&pack_context, bitmap, font->texture_size, font->texture_size, 0, 1, 0);
|
||||
stbtt_PackSetOversampling(&pack_context, 1, 1);
|
||||
if(!stbtt_PackFontRange(&pack_context, buffer, 0, size, 32, 95, font->chars)) {
|
||||
// too small
|
||||
free(bitmap);
|
||||
stbtt_PackEnd(&pack_context);
|
||||
font->texture_size *= 2;
|
||||
} else {
|
||||
stbtt_PackEnd(&pack_context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// convert bitmap to texture
|
||||
font->atlas = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, font->texture_size, font->texture_size);
|
||||
SDL_SetTextureBlendMode(font->atlas, SDL_BLENDMODE_BLEND);
|
||||
|
||||
Uint32* pixels = malloc(font->texture_size * font->texture_size * sizeof(Uint32));
|
||||
static SDL_PixelFormat* format = NULL;
|
||||
if(format == NULL) format = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA32);
|
||||
for(int i = 0; i < font->texture_size * font->texture_size; i++) {
|
||||
pixels[i] = SDL_MapRGBA(format, 0xff, 0xff, 0xff, bitmap[i]);
|
||||
}
|
||||
SDL_UpdateTexture(font->atlas, NULL, pixels, font->texture_size * sizeof(Uint32));
|
||||
free(pixels);
|
||||
free(bitmap);
|
||||
|
||||
// setup additional info
|
||||
font->scale = stbtt_ScaleForPixelHeight(font->info, size);
|
||||
stbtt_GetFontVMetrics(font->info, &font->ascent, 0, 0);
|
||||
font->baseline = (int) (font->ascent * font->scale);
|
||||
|
||||
free(buffer);
|
||||
|
||||
return font;
|
||||
}
|
||||
|
||||
STBTTF_Font* STBTTF_OpenFont(SDL_Renderer* renderer, const char* filename, float size) {
|
||||
SDL_RWops *rw = SDL_RWFromFile(filename, "rb");
|
||||
if(rw == NULL) return NULL;
|
||||
return STBTTF_OpenFontRW(renderer, rw, size);
|
||||
}
|
||||
|
||||
void STBTTF_RenderText(SDL_Renderer* renderer, STBTTF_Font* font, float x, float y, const char *text) {
|
||||
Uint8 r, g, b, a;
|
||||
SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a);
|
||||
SDL_SetTextureColorMod(font->atlas, r, g, b);
|
||||
SDL_SetTextureAlphaMod(font->atlas, a);
|
||||
for(int i = 0; text[i]; i++) {
|
||||
if (text[i] >= 32 && text[i] < 128) {
|
||||
//if(i > 0) x += stbtt_GetCodepointKernAdvance(font->info, text[i - 1], text[i]) * font->scale;
|
||||
|
||||
stbtt_packedchar* info = &font->chars[text[i] - 32];
|
||||
SDL_Rect src_rect = {info->x0, info->y0, info->x1 - info->x0, info->y1 - info->y0};
|
||||
SDL_Rect dst_rect = {x + info->xoff, y + info->yoff, info->x1 - info->x0, info->y1 - info->y0};
|
||||
|
||||
SDL_RenderCopy(renderer, font->atlas, &src_rect, &dst_rect);
|
||||
x += info->xadvance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float STBTTF_MeasureText(STBTTF_Font* font, const char *text) {
|
||||
float width = 0;
|
||||
for(int i = 0; text[i]; i++) {
|
||||
if (text[i] >= 32 && text[i] < 128) {
|
||||
//if(i > 0) width += stbtt_GetCodepointKernAdvance(font->info, text[i - 1], text[i]) * font->scale;
|
||||
|
||||
stbtt_packedchar* info = &font->chars[text[i] - 32];
|
||||
width += info->xadvance;
|
||||
}
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
/*******************
|
||||
* Example program *
|
||||
*******************
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#define STB_RECT_PACK_IMPLEMENTATION
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#define STBTTF_IMPLEMENTATION
|
||||
|
||||
#include "stbttf.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if(argc != 2) {
|
||||
fprintf(stderr, "usage: %s <font>\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
SDL_Init(SDL_INIT_VIDEO);
|
||||
SDL_Window* window = SDL_CreateWindow("stbttf", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL);
|
||||
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
||||
SDL_RenderSetLogicalSize(renderer, 640, 480);
|
||||
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
||||
|
||||
STBTTF_Font* font = STBTTF_OpenFont(renderer, argv[1], 32);
|
||||
|
||||
while(1) {
|
||||
SDL_Event event;
|
||||
while(SDL_PollEvent(&event)) {
|
||||
if(event.type == SDL_QUIT) exit(0);
|
||||
}
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
||||
SDL_RenderClear(renderer);
|
||||
// set color and render some text
|
||||
SDL_SetRenderDrawColor(renderer, 128, 0, 0, 255);
|
||||
STBTTF_RenderText(renderer, font, 50, 50, "This is a test");
|
||||
// render the atlas to check its content
|
||||
//SDL_Rect dest = {0, 0, font->texturesize, font->texturesize};
|
||||
//SDL_RenderCopy(renderer, font->atlas, &dest, &dest);
|
||||
SDL_RenderPresent(renderer);
|
||||
SDL_Delay(1000 / 60);
|
||||
}
|
||||
STBTTF_CloseFont(font);
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
4
text_rendering/.gitignore
vendored
4
text_rendering/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
*.png
|
||||
test
|
||||
obj/**
|
||||
objlist
|
||||
@ -1,18 +0,0 @@
|
||||
CC = gcc
|
||||
# instrumentation flags
|
||||
INFLAGS = -fsanitize=address,builtin,undefined
|
||||
LDFLAGS = -lm -lgrapheme -lSDL2 -lGLEW -lGL ${INFLAGS}
|
||||
CFLAGS = -ggdb3 -Wall -Wextra -pedantic -std=c11 -Wno-unused-function -fno-omit-frame-pointer ${INFLAGS}
|
||||
|
||||
.PHONY: clean all
|
||||
all: test
|
||||
|
||||
font.o: font.c font.h stb_truetype.h stb_image_write.h util.h \
|
||||
generic_cache.h generic_hash.h
|
||||
main.o: main.c ren.h util.h
|
||||
ren.o: ren.c util.h font.h ren.h generic_stack.h
|
||||
util.o: util.c util.h
|
||||
test: font.o main.o ren.o util.o
|
||||
${CC} ${LDFLAGS} -o test font.o main.o ren.o util.o
|
||||
clean:
|
||||
rm -f test font.o main.o ren.o util.o
|
||||
@ -1,8 +0,0 @@
|
||||
#version 330 core
|
||||
|
||||
in vec4 col;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = col;
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
#version 330 core
|
||||
|
||||
uniform ivec2 viewsize;
|
||||
|
||||
layout(location = 0) in vec2 position;
|
||||
layout(location = 2) in vec4 color;
|
||||
|
||||
out vec4 col;
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 v = vec2(float(viewsize.x), float(viewsize.y));
|
||||
vec2 p = vec2(position.x*2.0/v.x - 1.0, 1.0 - position.y*2.0/v.y);
|
||||
vec4 pos = vec4(p.x, p.y, 0.0f, 1.0f);
|
||||
|
||||
gl_Position = pos;
|
||||
col = color;
|
||||
}
|
||||
@ -1,195 +0,0 @@
|
||||
#define _POSIX_C_SOURCE 200809l
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#define STBTT_STATIC
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
|
||||
#include <grapheme.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "font.h"
|
||||
#include "stb_truetype.h"
|
||||
#include "stb_image_write.h"
|
||||
#include "util.h"
|
||||
|
||||
#include "generic_cache.h"
|
||||
static inline unsigned int hash(unsigned int code)
|
||||
{
|
||||
// identity map the ascii range
|
||||
return code < 128 ? code : hash_u32(code);
|
||||
}
|
||||
CACHE_DECL(cache, struct font_glyph, hash, hash_cmp_u32)
|
||||
|
||||
|
||||
#define UTF8(c) (c&0x80)
|
||||
#define BDEPTH 1
|
||||
#define BORDER 4
|
||||
|
||||
// FIXME: as of now only monospaced fonts work correctly since no kerning information
|
||||
// is stored
|
||||
|
||||
|
||||
struct priv {
|
||||
stbtt_fontinfo stb;
|
||||
float scale;
|
||||
int baseline;
|
||||
unsigned char *bitmap;
|
||||
struct cache c;
|
||||
};
|
||||
#define PRIV(x) ((struct priv *)x->priv)
|
||||
|
||||
|
||||
struct font_atlas * font_init(void)
|
||||
{
|
||||
struct font_atlas *p = emalloc(sizeof(struct font_atlas));
|
||||
memset(p, 0, sizeof(struct font_atlas));
|
||||
p->priv = emalloc(sizeof(struct priv));
|
||||
memset(p->priv, 0, sizeof(struct priv));
|
||||
PRIV(p)->c = cache_init();
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
// loads a font into memory, storing all the ASCII characters in the atlas, each font
|
||||
// atlas structure holds glyphs of a specific size in pixels
|
||||
// NOTE: size includes ascend and descend (so 12 does not mean that 'A' is 12px tall)
|
||||
int font_load(struct font_atlas *atlas, const char *path, int size)
|
||||
{
|
||||
if (!atlas || !path)
|
||||
return -1;
|
||||
|
||||
int err;
|
||||
|
||||
dump_file(path, &(atlas->file), &(atlas->file_size));
|
||||
|
||||
err = stbtt_InitFont(&(PRIV(atlas)->stb), (unsigned char *)atlas->file, 0);
|
||||
if (err == 0) return -1;
|
||||
|
||||
int ascent, descent, linegap, baseline;
|
||||
int x0,y0,x1,y1;
|
||||
float scale;
|
||||
stbtt_GetFontVMetrics(&(PRIV(atlas)->stb), &ascent, &descent, &linegap);
|
||||
stbtt_GetFontBoundingBox(&(PRIV(atlas)->stb), &x0, &y0, &x1, &y1);
|
||||
scale = stbtt_ScaleForPixelHeight(&(PRIV(atlas)->stb), size);
|
||||
baseline = scale * -y0;
|
||||
atlas->glyph_max_w = (scale*x1) - (scale*x0);
|
||||
atlas->glyph_max_h = (baseline+scale*y1) - (baseline+scale*y0);
|
||||
atlas->atlas = emalloc(CACHE_SIZE*BDEPTH*atlas->glyph_max_w*atlas->glyph_max_h);
|
||||
memset(atlas->atlas, 0, CACHE_SIZE*BDEPTH*atlas->glyph_max_w*atlas->glyph_max_h);
|
||||
PRIV(atlas)->baseline = atlas->glyph_max_h - baseline;
|
||||
PRIV(atlas)->scale = scale;
|
||||
PRIV(atlas)->bitmap = emalloc(BDEPTH*atlas->glyph_max_w*atlas->glyph_max_h);
|
||||
// FIXME: make this a square atlas
|
||||
atlas->width = atlas->glyph_max_w*CACHE_SIZE/4;
|
||||
atlas->height = atlas->glyph_max_h*4;
|
||||
atlas->size = size;
|
||||
|
||||
// preallocate all ascii characters
|
||||
for (char c = ' '; c <= '~'; c++) {
|
||||
if (!font_get_glyph_texture(atlas, c, NULL))
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int font_free(struct font_atlas *atlas)
|
||||
{
|
||||
efree(atlas->atlas);
|
||||
efree(atlas->file);
|
||||
efree(PRIV(atlas)->bitmap);
|
||||
cache_free(&PRIV(atlas)->c);
|
||||
efree(atlas->priv);
|
||||
efree(atlas);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// TODO: time and take the median of the time it takes to generate the cache and
|
||||
// the time it takes to draw the glyph
|
||||
const struct font_glyph * font_get_glyph_texture(struct font_atlas *atlas, unsigned int code, int *updated)
|
||||
{
|
||||
int _u = 0;
|
||||
if (!updated) updated = &_u;
|
||||
|
||||
const struct font_glyph *r;
|
||||
if ((r = cache_search(&PRIV(atlas)->c, code)) != NULL) {
|
||||
*updated = 0;
|
||||
return r;
|
||||
}
|
||||
|
||||
*updated = 1;
|
||||
// generate the sdf and put it into the cache
|
||||
// TODO: generate the whole block at once
|
||||
int idx = stbtt_FindGlyphIndex(&PRIV(atlas)->stb, code);
|
||||
int x0,y0,x1,y1,gw,gh,l,off_x,off_y,adv,base;
|
||||
base = atlas->glyph_max_h - PRIV(atlas)->baseline;
|
||||
stbtt_GetGlyphBitmapBoxSubpixel(
|
||||
&PRIV(atlas)->stb,
|
||||
idx,
|
||||
PRIV(atlas)->scale,
|
||||
PRIV(atlas)->scale,
|
||||
0,0,
|
||||
&x0,&y0,
|
||||
&x1, &y1);
|
||||
gw = x1 - x0;
|
||||
gh = y1 - y0;
|
||||
stbtt_GetGlyphHMetrics(&PRIV(atlas)->stb, idx, &adv, &l);
|
||||
adv *= PRIV(atlas)->scale;
|
||||
off_x = PRIV(atlas)->scale*l;
|
||||
off_y = atlas->glyph_max_h+y0;
|
||||
stbtt_MakeGlyphBitmapSubpixel(
|
||||
&PRIV(atlas)->stb,
|
||||
PRIV(atlas)->bitmap,
|
||||
atlas->glyph_max_w,
|
||||
atlas->glyph_max_h,
|
||||
atlas->glyph_max_w,
|
||||
PRIV(atlas)->scale,
|
||||
PRIV(atlas)->scale,
|
||||
0, 0,
|
||||
idx);
|
||||
|
||||
// TODO: bounds check usign atlas height
|
||||
// TODO: clear spot area in the atlas before writing on it
|
||||
unsigned int spot = cache_get_free_spot(&PRIV(atlas)->c);
|
||||
unsigned int ty = ((atlas->glyph_max_w * spot) / atlas->width) * atlas->glyph_max_h;
|
||||
unsigned int tx = (atlas->glyph_max_w * spot) % atlas->width;
|
||||
unsigned int w = atlas->width;
|
||||
|
||||
unsigned char *a = (void *)atlas->atlas;
|
||||
|
||||
//printf("max:%d %d spot:%d : %d %d %d %d\n", atlas->glyph_max_w, atlas->glyph_max_h, spot, tx, ty, off_x, off_y);
|
||||
|
||||
for (int y = 0; y < gh; y++) {
|
||||
for (int x = 0; x < gw; x++) {
|
||||
int c, r;
|
||||
r = (ty+y)*w;
|
||||
c = tx+x;
|
||||
a[r+c] = PRIV(atlas)->bitmap[y*atlas->glyph_max_w+x];
|
||||
}
|
||||
}
|
||||
|
||||
struct font_glyph g = {
|
||||
.codepoint = code,
|
||||
.u = tx,
|
||||
.v = ty,
|
||||
.w = gw,
|
||||
.h = gh,
|
||||
.x = off_x,
|
||||
.y = off_y-base,
|
||||
.a = adv,
|
||||
};
|
||||
return cache_insert_at(&PRIV(atlas)->c, &g, g.codepoint, spot);
|
||||
}
|
||||
|
||||
|
||||
void font_dump(const struct font_atlas *atlas, const char *path)
|
||||
{
|
||||
stbi_write_png(
|
||||
path,
|
||||
atlas->width,
|
||||
atlas->height,
|
||||
BDEPTH,
|
||||
atlas->atlas,
|
||||
BDEPTH*atlas->width);
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
#ifndef _FONT_H
|
||||
#define _FONT_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* width and height of a glyph contain the kering advance
|
||||
* (u,v)
|
||||
* +-------------*---+ -
|
||||
* | ^ | | ^
|
||||
* | |oy | | |
|
||||
* | v | | |
|
||||
* | .ii. | | |
|
||||
* | @@@@@@. |<->| |
|
||||
* | V@Mio@@o |adv| |
|
||||
* | :i. V@V | | |
|
||||
* | :oM@@M | | |
|
||||
* | :@@@MM@M | | |
|
||||
* | @@o o@M | | |
|
||||
* |<->:@@. M@M | | |
|
||||
* |ox @@@o@@@@ | | |
|
||||
* | :M@@V:@@.| | v
|
||||
* +-------------*---+ -
|
||||
* |<------------->|
|
||||
* w
|
||||
*/
|
||||
// TODO: the advance isn't unique for every pair of characters
|
||||
struct font_glyph {
|
||||
uint32_t codepoint;
|
||||
uint32_t u, v;
|
||||
uint16_t w, h, a, x, y;
|
||||
};
|
||||
|
||||
struct font_atlas {
|
||||
unsigned int width, height;
|
||||
unsigned char *atlas;
|
||||
unsigned int glyph_max_w, glyph_max_h;
|
||||
int size;
|
||||
int file_size;
|
||||
char *file;
|
||||
void *priv;
|
||||
};
|
||||
|
||||
|
||||
struct font_atlas * font_init(void);
|
||||
int font_load(struct font_atlas *atlas, const char *path, int size);
|
||||
int font_free(struct font_atlas *atlas);
|
||||
const struct font_glyph * font_get_glyph_texture(struct font_atlas *atlas, unsigned int code, int *updated);
|
||||
void font_dump(const struct font_atlas *atlas, const char *path);
|
||||
|
||||
#endif
|
||||
@ -1,18 +0,0 @@
|
||||
#version 330 core
|
||||
|
||||
// viewsize.x = viewport width in pixels; viewsize.y = viewport height in pixels
|
||||
uniform ivec2 viewsize;
|
||||
uniform ivec2 texturesize;
|
||||
|
||||
// texture uv coordinate in texture space
|
||||
in vec2 uv;
|
||||
uniform sampler2DRect ts;
|
||||
|
||||
const vec3 textcolor = vec3(1.0, 1.0, 1.0);
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
//gl_FragColor = vec4(1.0f,0.0f,0.0f,1.0f);
|
||||
gl_FragColor = vec4(textcolor, texture(ts, uv));
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
#version 330 core
|
||||
|
||||
// viewsize.x = viewport width in pixels; viewsize.y = viewport height in pixels
|
||||
uniform ivec2 viewsize;
|
||||
uniform ivec2 texturesize;
|
||||
|
||||
// both position and and uv are in pixels, they where converted to floats when
|
||||
// passed to the shader
|
||||
layout(location = 0) in vec2 position;
|
||||
layout(location = 1) in vec2 txcoord;
|
||||
|
||||
out vec2 uv;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 v = vec2(float(viewsize.x), float(viewsize.y));
|
||||
|
||||
// vec2 p = vec2(position.x*2.0f/v.x - 1.0f, position.y*2.0f/v.y - 1.0f);
|
||||
vec2 p = vec2(position.x*2.0/v.x - 1.0, 1.0 - position.y*2.0/v.y);
|
||||
vec4 pos = vec4(p.x, p.y, 0.0f, 1.0f);
|
||||
|
||||
gl_Position = pos;
|
||||
// since the texture is a GL_TEXTURE_RECTANGLE the coordintes do not need to
|
||||
// be normalized
|
||||
uv = vec2(txcoord.x, txcoord.y);
|
||||
}
|
||||
@ -1,126 +0,0 @@
|
||||
#ifndef CACHE_GENERIC_H
|
||||
#define CACHE_GENERIC_H
|
||||
|
||||
|
||||
#include "generic_hash.h"
|
||||
|
||||
#define CACHE_SIZE 512
|
||||
#define CACHE_NCYCLES (CACHE_SIZE*2/3)
|
||||
#define CACHE_BSIZE (((CACHE_SIZE+0x3f)&(~0x3f))>>6)
|
||||
#define CACHE_BRESET(b) for (int i = 0; i < CACHE_BSIZE; b[i++] = 0);
|
||||
#define CACHE_BSET(b, x) b[(x)>>6] |= (uint64_t)1<<((x)&63)
|
||||
#define CACHE_BTEST(b, x) (b[(x)>>6]&((uint64_t)1<<((x)&63)))
|
||||
|
||||
// FIXME: this cache implementation is not really generic since it expects an unsigned
|
||||
// as the code and not a generic type
|
||||
|
||||
|
||||
#define CACHE_CYCLE(c) \
|
||||
{ \
|
||||
if (++(c->cycles) > CACHE_NCYCLES) { \
|
||||
for (int i = 0; i < CACHE_BSIZE; i++) { \
|
||||
c->present[i] &= c->used[i]; \
|
||||
c->used[i] = 0; \
|
||||
} \
|
||||
c->cycles = 0; \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
#define CACHE_DECL(name, type, hashfn, cmpfn) \
|
||||
HASH_DECL(name##table, uint32_t, uint32_t, hashfn, cmpfn) \
|
||||
struct name { \
|
||||
struct name##table_ref *table; \
|
||||
type *array; \
|
||||
uint64_t *present, *used; \
|
||||
int cycles; \
|
||||
}; \
|
||||
\
|
||||
\
|
||||
/* FIXME: check for allocation errors */ \
|
||||
struct name name##_init(void) \
|
||||
{ \
|
||||
struct name##table_ref *t = name##table_create(CACHE_SIZE); \
|
||||
type *a = malloc(sizeof(type)*CACHE_SIZE); \
|
||||
uint64_t *p = malloc(sizeof(uint64_t)*CACHE_BSIZE); \
|
||||
uint64_t *u = malloc(sizeof(uint64_t)*CACHE_BSIZE); \
|
||||
CACHE_BRESET(p); \
|
||||
CACHE_BRESET(u); \
|
||||
return (struct name){.table=t, .array=a, .present=p, .used=u, 0}; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
void name##_free(struct name *cache) \
|
||||
{ \
|
||||
if (cache) { \
|
||||
name##table_destroy(cache->table); \
|
||||
free(cache->array); \
|
||||
free(cache->present); \
|
||||
free(cache->used); \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
\
|
||||
const type * name##_search(struct name *cache, uint32_t code) \
|
||||
{ \
|
||||
if (!cache) return NULL; \
|
||||
struct name##table_entry *r; \
|
||||
r = name##table_search(cache->table, code); \
|
||||
/* MISS */ \
|
||||
if (!r || !cmpfn(code, r->code)) \
|
||||
return NULL; \
|
||||
/* MISS, the data is not valid (not present) */ \
|
||||
if (!CACHE_BTEST(cache->present, r->data)) \
|
||||
return NULL; \
|
||||
/* HIT, set as recently used */ \
|
||||
CACHE_BSET(cache->used, r->data); \
|
||||
return (&cache->array[r->data]); \
|
||||
} \
|
||||
\
|
||||
\
|
||||
/* Look for a free spot in the present bitmap and return its index */ \
|
||||
/* If there is no free space left then just return the first position */ \
|
||||
int name##_get_free_spot(struct name *cache) \
|
||||
{ \
|
||||
if (!cache) return -1; \
|
||||
int x = 0; \
|
||||
for (int b = 0; b < CACHE_BSIZE; b++) { \
|
||||
if (cache->present[b] == 0) x = 64; \
|
||||
else x = __builtin_clzll(cache->present[b]); \
|
||||
x = 64-x; \
|
||||
if (!CACHE_BTEST(cache->present, x+64*b)) \
|
||||
return x+64*b; \
|
||||
} \
|
||||
return 0; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
const type * name##_insert_at(struct name *cache, const type *g, uint32_t code, int x)\
|
||||
{ \
|
||||
if (!cache) return NULL; \
|
||||
type *spot = NULL; \
|
||||
/* check if the spot is valid */ \
|
||||
if (x < 0) return NULL; \
|
||||
/* Set used and present */ \
|
||||
CACHE_BSET(cache->present, x); \
|
||||
CACHE_BSET(cache->used, x); \
|
||||
CACHE_CYCLE(cache) \
|
||||
spot = &(cache->array[x]); \
|
||||
*spot = *g; \
|
||||
struct name##table_entry e = { .code = code, .data = x}; \
|
||||
if (!name##table_insert(cache->table, &e)) \
|
||||
return NULL; \
|
||||
return spot; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
const type * name##_insert(struct name *cache, const type *g, uint32_t code, int *x)\
|
||||
{ \
|
||||
int y; \
|
||||
if (!x) x = &y; \
|
||||
*x = name##_get_free_spot(cache); \
|
||||
return name##_insert_at(cache, g, code, *x); \
|
||||
} \
|
||||
|
||||
|
||||
#endif
|
||||
@ -1,134 +0,0 @@
|
||||
#ifndef _HASH_GENERIC
|
||||
#define _HASH_GENERIC
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
#define HASH_MAXSIZE 4096
|
||||
// for fibonacci hashing, 2^{32,64}/<golden ratio>
|
||||
#define HASH_RATIO32 ((uint64_t)2654435769u)
|
||||
#define HASH_RATIO64 ((uint64_t)11400714819322457583u)
|
||||
// salt for string hashing
|
||||
#define HASH_SALT ((uint64_t)0xbabb0cac)
|
||||
|
||||
|
||||
/* Ready-made compares */
|
||||
static inline int hash_cmp_u32(uint32_t a, uint32_t b) { return a == b; }
|
||||
static inline int hash_cmp_u64(uint64_t a, uint64_t b) { return a == b; }
|
||||
static inline int hash_cmp_str(const char *a, const char *b) { return strcmp(a, b) == 0; }
|
||||
|
||||
|
||||
/* Ready-made hashes */
|
||||
static inline uint32_t hash_u64(uint64_t c)
|
||||
{
|
||||
return (uint64_t)((((uint64_t)c+HASH_SALT)*HASH_RATIO64)>>32);
|
||||
}
|
||||
static inline uint32_t hash_u32(uint32_t c)
|
||||
{
|
||||
return (uint32_t)((((uint64_t)c<<31)*HASH_RATIO64)>>32);
|
||||
}
|
||||
static inline uint32_t hash_str(const char *s)
|
||||
{
|
||||
uint32_t h = HASH_SALT;
|
||||
const uint8_t *v = (const uint8_t *)(s);
|
||||
for (int x = *s; x; x--) {
|
||||
h += v[x-1];
|
||||
h += h << 10;
|
||||
h ^= h >> 6;
|
||||
}
|
||||
h += h << 3;
|
||||
h ^= h >> 11;
|
||||
h += h << 15;
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
#define HASH_DECL(htname, codetype, datatype, hashfn, cmpfn) \
|
||||
struct htname##_entry { \
|
||||
codetype code; \
|
||||
datatype data; \
|
||||
}; \
|
||||
\
|
||||
struct htname##_ref { \
|
||||
uint32_t items, size, exp; \
|
||||
struct htname##_entry bucket[]; \
|
||||
}; \
|
||||
\
|
||||
\
|
||||
struct htname##_ref * htname##_create(uint32_t size) \
|
||||
{ \
|
||||
if (!size || size > HASH_MAXSIZE) \
|
||||
return NULL; \
|
||||
/* round to the greater power of two */ \
|
||||
/* FIXME: check for intger overflow here */ \
|
||||
uint32_t exp = 32-__builtin_clz(size-1); \
|
||||
size = 1<<(exp); \
|
||||
/* FIXME: check for intger overflow here */ \
|
||||
struct htname##_ref *ht = malloc(sizeof(struct htname##_ref)+sizeof(struct htname##_entry)*size); \
|
||||
if (ht) { \
|
||||
ht->items = 0; \
|
||||
ht->size = size; \
|
||||
ht->exp = exp; \
|
||||
memset(ht->bucket, 0, sizeof(struct htname##_entry)*size); \
|
||||
} \
|
||||
return ht; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
void htname##_destroy(struct htname##_ref *ht) \
|
||||
{ \
|
||||
if (ht) free(ht); \
|
||||
} \
|
||||
\
|
||||
\
|
||||
static uint32_t htname##_lookup(struct htname##_ref *ht, uint32_t hash, uint32_t idx) \
|
||||
{ \
|
||||
if (!ht) return 0; \
|
||||
uint32_t mask = ht->size-1; \
|
||||
uint32_t step = (hash >> (32 - ht->exp)) | 1; \
|
||||
return (idx + step) & mask; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
/* Find and return the element by code */ \
|
||||
struct htname##_entry * htname##_search(struct htname##_ref *ht, codetype code)\
|
||||
{ \
|
||||
if (!ht) return NULL; \
|
||||
uint32_t h = hashfn(code); \
|
||||
for (uint32_t i=h, x=0; ; x++) { \
|
||||
i = htname##_lookup(ht, h, i); \
|
||||
if (x > (ht->size<<1) || \
|
||||
!ht->bucket[i].code || \
|
||||
cmpfn(ht->bucket[i].code, code) \
|
||||
) { \
|
||||
return &(ht->bucket[i]); \
|
||||
} \
|
||||
} \
|
||||
return NULL; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
/* FIXME: this simply overrides the found item */ \
|
||||
struct htname##_entry * htname##_insert(struct htname##_ref *ht, struct htname##_entry *entry) \
|
||||
{ \
|
||||
struct htname##_entry *r = htname##_search(ht, entry->code); \
|
||||
if (r) { \
|
||||
if (!r->code) \
|
||||
ht->items++; \
|
||||
*r = *entry; \
|
||||
} \
|
||||
return r; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
struct htname##_entry * htname##_remove(struct htname##_ref *ht, codetype code)\
|
||||
{ \
|
||||
if (!ht) return NULL; \
|
||||
struct htname##_entry *r = htname##_search(ht, code); \
|
||||
if (r) r->code = 0; \
|
||||
return r; \
|
||||
} \
|
||||
|
||||
#endif
|
||||
@ -1,118 +0,0 @@
|
||||
#ifndef _STACK_GENERIC_H
|
||||
#define _STACK_GENERIC_H
|
||||
|
||||
|
||||
#define STACK_STEP 8
|
||||
#define STACK_SALT 0xbabb0cac
|
||||
|
||||
// FIXME: find a way to not re-hash the whole stack when removing one item
|
||||
|
||||
// incremental hash for every grow
|
||||
#if STACK_DISABLE_HASH
|
||||
#define STACK_HASH(p, s, h) {}
|
||||
#else
|
||||
#define STACK_HASH(p, s, h) \
|
||||
{ \
|
||||
unsigned char *v = (unsigned char *)(p); \
|
||||
for (int x = (s); x; x--) { \
|
||||
(h) += v[x-1]; \
|
||||
(h) += (h) << 10; \
|
||||
(h) ^= (h) >> 6; \
|
||||
} \
|
||||
(h) += (h) << 3; \
|
||||
(h) ^= (h) >> 11; \
|
||||
(h) += (h) << 15; \
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// TODO: add a rolling hash
|
||||
#define STACK_DECL(stackname, type) \
|
||||
struct stackname { \
|
||||
type *items; \
|
||||
int size, idx, old_idx; \
|
||||
unsigned int hash, old_hash; \
|
||||
}; \
|
||||
\
|
||||
\
|
||||
struct stackname stackname##_init(void) \
|
||||
{ \
|
||||
return (struct stackname){0, .hash = STACK_SALT}; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_grow(struct stackname *stack, int step) \
|
||||
{ \
|
||||
if (!stack) \
|
||||
return -1; \
|
||||
stack->items = realloc(stack->items, (stack->size+step)*sizeof(type)); \
|
||||
if(!stack->items) \
|
||||
return -1; \
|
||||
memset(&(stack->items[stack->size]), 0, step*sizeof(*(stack->items))); \
|
||||
stack->size += step; \
|
||||
return 0; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_push(struct stackname *stack, type *e) \
|
||||
{ \
|
||||
if (!stack || !e) \
|
||||
return -1; \
|
||||
if (stack->idx >= stack->size) \
|
||||
if (stackname##_grow(stack, STACK_STEP)) \
|
||||
return -1; \
|
||||
stack->items[stack->idx++] = *e; \
|
||||
STACK_HASH(e, sizeof(type), stack->hash); \
|
||||
return 0; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
type stackname##_pop(struct stackname *stack) \
|
||||
{ \
|
||||
if (!stack || stack->idx == 0 || stack->size == 0) \
|
||||
return (type){0}; \
|
||||
stack->hash = STACK_SALT; \
|
||||
STACK_HASH(stack->items, sizeof(type)*(stack->idx-1), stack->hash); \
|
||||
return stack->items[stack->idx--]; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_clear(struct stackname *stack) \
|
||||
{ \
|
||||
if (!stack) \
|
||||
return -1; \
|
||||
stack->old_idx = stack->idx; \
|
||||
stack->old_hash = stack->hash; \
|
||||
stack->hash = STACK_SALT; \
|
||||
stack->idx = 0; \
|
||||
return 0; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_changed(struct stackname *stack) \
|
||||
{ \
|
||||
if (!stack) \
|
||||
return -1; \
|
||||
return stack->hash != stack->old_hash; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_size_changed(struct stackname *stack) \
|
||||
{ \
|
||||
if (!stack) \
|
||||
return -1; \
|
||||
return stack->size != stack->old_idx; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_free(struct stackname *stack) \
|
||||
{ \
|
||||
if (stack) { \
|
||||
stackname##_clear(stack); \
|
||||
if (stack->items) \
|
||||
free(stack->items); \
|
||||
} \
|
||||
return 0; \
|
||||
} \
|
||||
|
||||
#endif
|
||||
@ -1,46 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
genobj() {
|
||||
#echo "$1" | sed -e 's/^/obj\//' -e 's/\.c/\.o/'
|
||||
echo "$1" | sed -e 's/\.c/\.o/'
|
||||
}
|
||||
|
||||
genrule() {
|
||||
#gcc -MM "$1" | sed 's/^/obj\//'
|
||||
gcc -MM "$1"
|
||||
}
|
||||
|
||||
|
||||
rm -f objlist
|
||||
|
||||
cat > Makefile << EOF
|
||||
CC = gcc
|
||||
# instrumentation flags
|
||||
INFLAGS = -fsanitize=address,builtin,undefined
|
||||
LDFLAGS = -lm -lgrapheme -lSDL2 -lGLEW -lGL \${INFLAGS}
|
||||
CFLAGS = -ggdb3 -Wall -Wextra -pedantic -std=c11 \
|
||||
-Wno-unused-function -fno-omit-frame-pointer \${INFLAGS}
|
||||
|
||||
.PHONY: clean all
|
||||
all: test
|
||||
|
||||
EOF
|
||||
|
||||
for f in *.c; do
|
||||
genrule $f >> Makefile
|
||||
genobj $f >> objlist
|
||||
done
|
||||
|
||||
mainrule='test: '
|
||||
linkcmd=' ${CC} ${LDFLAGS} -o test '
|
||||
cleanrule='clean:
|
||||
rm -f test '
|
||||
while IFS="" read -r line; do
|
||||
mainrule="$mainrule $line"
|
||||
linkcmd="$linkcmd $line"
|
||||
cleanrule="$cleanrule $line"
|
||||
done < objlist
|
||||
|
||||
echo "$mainrule" >> Makefile
|
||||
echo "$linkcmd" >> Makefile
|
||||
echo "$cleanrule" >> Makefile
|
||||
@ -1,96 +0,0 @@
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_events.h>
|
||||
#include <SDL2/SDL_video.h>
|
||||
#include <stdlib.h>
|
||||
#include <locale.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "ren.h"
|
||||
#include "util.h"
|
||||
|
||||
|
||||
//const char *str1 = "Ciao Mamma!\nprova: òçà°ù§|¬³¼$£ì\t";
|
||||
const char *str1 = "ciao\tmamma";
|
||||
const char *str2 = "The quick brown fox jumps over the lazy dog\n"
|
||||
"чащах юга жил бы цитрус? Да, но фальшивый экземпляр!\n"
|
||||
"Pijamalı hasta, yağız şoföre çabucak güvendi\n"
|
||||
"Pchnąć w tę łódź jeża lub ośm skrzyń fig\n"
|
||||
"イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム ウヰノオクヤマ ケフコエテ アサキユメミシ ヱヒモセスン\n"
|
||||
"Kæmi ný öxi hér ykist þjófum nú bæði víl og ádrepa";
|
||||
SDL_Window *win;
|
||||
|
||||
|
||||
#define red 0xff0000ff
|
||||
#define blue 0xffff0000
|
||||
void draw(void)
|
||||
{
|
||||
static unsigned int frame = 0;
|
||||
printf("frame: %d\n", frame++);
|
||||
ren_clear();
|
||||
//ren_render_box(10, 10, 400, 50, blue);
|
||||
//if (ren_render_text(str1, 10, 10, 400, 50, 20))
|
||||
// printf("text: %s\n", ren_strerror());
|
||||
int w, h;
|
||||
ren_get_text_box(str2, &w, &h, 20);
|
||||
//printf("box for: %s -> (%d, %d)\n", str2, w, h);
|
||||
ren_render_box(0, 0, w, h, blue);
|
||||
ren_render_text(str2, 0, 0, w, h, 20);
|
||||
|
||||
// fixme: this causes a bug
|
||||
const char *s = "ciao mamma";
|
||||
ren_get_text_box(s, &w, &h, 12);
|
||||
s = "stuff that was not in font size 12 -> чащаx";
|
||||
ren_render_box(0, 200, w, h, red);
|
||||
if (ren_render_text(s, 0, 200, 0xffff, 0xffff, 12))
|
||||
printf("BUG\n");
|
||||
|
||||
SDL_GL_SwapWindow(win);
|
||||
}
|
||||
|
||||
|
||||
int main(void)
|
||||
{
|
||||
setlocale(LC_ALL, "en_US.UTF-8");
|
||||
|
||||
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
|
||||
SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
|
||||
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
|
||||
SDL_SetHint(SDL_HINT_VIDEO_HIGHDPI_DISABLED, "0");
|
||||
|
||||
win = SDL_CreateWindow(
|
||||
"test render",
|
||||
SDL_WINDOWPOS_UNDEFINED,
|
||||
SDL_WINDOWPOS_UNDEFINED,
|
||||
500,
|
||||
500,
|
||||
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||
if (ren_init(win)) {
|
||||
printf("renderer init error: %s\n", ren_strerror());
|
||||
return 1;
|
||||
}
|
||||
|
||||
SDL_Event e;
|
||||
while(1) {
|
||||
SDL_WaitEvent(&e);
|
||||
if (e.type == SDL_QUIT)
|
||||
break;
|
||||
if (e.type == SDL_WINDOWEVENT) {
|
||||
switch (e.window.event) {
|
||||
case SDL_WINDOWEVENT_RESIZED:
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
ren_update_viewport(e.window.data1, e.window.data2);
|
||||
draw();
|
||||
break;
|
||||
case SDL_WINDOWEVENT_EXPOSED:
|
||||
draw();
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ren_free();
|
||||
SDL_DestroyWindow(win);
|
||||
SDL_Quit();
|
||||
return 0;
|
||||
}
|
||||
@ -1,826 +0,0 @@
|
||||
#include <SDL2/SDL.h>
|
||||
#include <GL/glew.h>
|
||||
#include <SDL2/SDL_opengl.h>
|
||||
#include <SDL2/SDL_video.h>
|
||||
#include <ctype.h>
|
||||
#include <grapheme.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "util.h"
|
||||
#include "font.h"
|
||||
#include "ren.h"
|
||||
|
||||
|
||||
#define GLERR(x) \
|
||||
{ \
|
||||
int a = glGetError(); \
|
||||
if (a != GL_NO_ERROR) \
|
||||
printf("(%s:%d %s:%s) glError: 0x%x %s\n", \
|
||||
__FILE__, __LINE__, __func__, x, a, glerr[a&0xff]); \
|
||||
}
|
||||
#define GL(f) f; GLERR(#f)
|
||||
#define REN_RET(a,b) {ren_errno = b; return a;}
|
||||
|
||||
|
||||
enum REN_ERR {
|
||||
REN_SUCCESS = 0,
|
||||
REN_ERRNO,
|
||||
REN_INVAL,
|
||||
REN_VERTEX,
|
||||
REN_FRAGMENT,
|
||||
REN_PROGRAM,
|
||||
REN_COMPILE,
|
||||
REN_LINK,
|
||||
REN_TEXTURE,
|
||||
REN_CONTEXT,
|
||||
REN_GLEW,
|
||||
REN_FONT,
|
||||
REN_BUFFER,
|
||||
REN_UNIFORM,
|
||||
};
|
||||
|
||||
|
||||
// TODO: make a macro for enum-associated string arrays
|
||||
const char * ren_err_msg[] = {
|
||||
[REN_SUCCESS] = "Success",
|
||||
[REN_ERRNO] = "Look at errno",
|
||||
[REN_INVAL] = "Invalid or NULL arguments",
|
||||
[REN_VERTEX] = "Failed to create opengl vertex shader",
|
||||
[REN_FRAGMENT] = "Failed to create opengl fragment shader",
|
||||
[REN_PROGRAM] = "Failed to create opengl program",
|
||||
[REN_COMPILE] = "Failed to compile shaders",
|
||||
[REN_LINK] = "Failed to link shaders",
|
||||
[REN_TEXTURE] = "Failed to create texture",
|
||||
[REN_CONTEXT] = "Failed to create SDL OpenGL context",
|
||||
[REN_GLEW] = "GLEW Error",
|
||||
[REN_FONT] = "Font Error",
|
||||
[REN_BUFFER] = "Failed to create opengl buffer",
|
||||
[REN_UNIFORM] = "Failed to get uniform location",
|
||||
};
|
||||
|
||||
#define ELEM(x) [x&0xff] = #x,
|
||||
const char *glerr[] = {
|
||||
ELEM(GL_INVALID_ENUM)
|
||||
ELEM(GL_INVALID_VALUE)
|
||||
ELEM(GL_INVALID_OPERATION)
|
||||
ELEM(GL_OUT_OF_MEMORY)
|
||||
};
|
||||
#undef ELEM
|
||||
|
||||
|
||||
// different stacks
|
||||
#include "generic_stack.h"
|
||||
STACK_DECL(vtstack, struct v_text)
|
||||
STACK_DECL(vcstack, struct v_col)
|
||||
|
||||
|
||||
struct ren_font {
|
||||
struct font_atlas *font;
|
||||
GLuint texture;
|
||||
};
|
||||
|
||||
struct {
|
||||
SDL_GLContext *gl;
|
||||
struct ren_font *fonts;
|
||||
int fonts_no;
|
||||
GLuint font_prog;
|
||||
GLuint box_prog;
|
||||
GLuint font_buffer;
|
||||
GLuint box_buffer;
|
||||
GLint viewsize_loc;
|
||||
GLint box_viewsize_loc;
|
||||
GLint texturesize_loc;
|
||||
struct vtstack font_stack;
|
||||
struct vcstack box_stack;
|
||||
int width, height;
|
||||
int s_x, s_y, s_w, s_h;
|
||||
int tabsize;
|
||||
} ren = {0};
|
||||
|
||||
|
||||
static int ren_errno;
|
||||
|
||||
|
||||
// print shader compilation errors
|
||||
static int shader_compile_error(GLuint shader, const char *path)
|
||||
{
|
||||
GLint status;
|
||||
GL(glGetShaderiv(shader, GL_COMPILE_STATUS, &status))
|
||||
if (status != GL_FALSE)
|
||||
return 0;
|
||||
|
||||
GLint log_length;
|
||||
GL(glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length))
|
||||
|
||||
GLchar *log_str = emalloc((log_length + 1)*sizeof(GLchar));
|
||||
GL(glGetShaderInfoLog(shader, log_length, NULL, log_str))
|
||||
|
||||
const char *shader_type_str = NULL;
|
||||
GLint shader_type;
|
||||
GL(glGetShaderiv(shader, GL_SHADER_TYPE, &shader_type))
|
||||
switch(shader_type) {
|
||||
case GL_VERTEX_SHADER: shader_type_str = "vertex"; break;
|
||||
case GL_GEOMETRY_SHADER: shader_type_str = "geometry"; break;
|
||||
case GL_FRAGMENT_SHADER: shader_type_str = "fragment"; break;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Compile failure in %s shader %s:\n%s\n", shader_type_str, path, log_str);
|
||||
efree(log_str);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// print shader link errors
|
||||
static int shader_link_error(GLuint prog)
|
||||
{
|
||||
GLint status;
|
||||
GL(glGetProgramiv(prog, GL_LINK_STATUS, &status))
|
||||
if (status != GL_FALSE)
|
||||
return 0;
|
||||
|
||||
GLint log_length;
|
||||
GL(glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &log_length))
|
||||
|
||||
GLchar *log_str = emalloc((log_length + 1)*sizeof(GLchar));
|
||||
GL(glGetProgramInfoLog(prog, log_length, NULL, log_str))
|
||||
fprintf(stderr, "Linker failure: %s\n", log_str);
|
||||
efree(log_str);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
static GLuint shader_compile(const char *path, const char *shader, GLuint type)
|
||||
{
|
||||
GLuint s;
|
||||
// initialize the vertex shader and get the corresponding id
|
||||
s = GL(glCreateShader(type))
|
||||
if (!s) REN_RET(0, REN_VERTEX)
|
||||
// get the shader into opengl
|
||||
GL(glShaderSource(s, 1, &shader, NULL))
|
||||
GL(glCompileShader(s))
|
||||
if (shader_compile_error(s, path))
|
||||
REN_RET(0, REN_COMPILE)
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
const char * ren_strerror(void)
|
||||
{
|
||||
return ren_err_msg[ren_errno % (sizeof(ren_err_msg)/sizeof(char *))];
|
||||
}
|
||||
|
||||
|
||||
// compile a vertex shader (vs_path) and a fragment shader (fs_path) into an opengl
|
||||
// program and return it's index
|
||||
static GLuint ren_compile_program(const char *vs_path, const char *fs_path)
|
||||
{
|
||||
GLuint gl_vertshader, gl_fragshader, prog;
|
||||
|
||||
if (!vs_path || !fs_path)
|
||||
REN_RET(0, REN_INVAL)
|
||||
|
||||
char *str = NULL;
|
||||
|
||||
dump_file(vs_path, &str, NULL);
|
||||
if (!str) REN_RET(0, REN_ERRNO)
|
||||
gl_vertshader = shader_compile(vs_path, str, GL_VERTEX_SHADER);
|
||||
efree(str);
|
||||
if (!gl_vertshader)
|
||||
return 0;
|
||||
|
||||
dump_file(fs_path, &str, NULL);
|
||||
if (!str) REN_RET(0, REN_ERRNO)
|
||||
gl_fragshader = shader_compile(fs_path, str, GL_FRAGMENT_SHADER);
|
||||
efree(str);
|
||||
if (!gl_fragshader)
|
||||
return 0;
|
||||
|
||||
|
||||
// create the main program object, it is an amalgamation of all shaders
|
||||
prog = GL(glCreateProgram())
|
||||
if (!prog) REN_RET(0, REN_PROGRAM)
|
||||
|
||||
// attach the shaders to the program (set which shaders are present)
|
||||
GL(glAttachShader(prog, gl_vertshader))
|
||||
GL(glAttachShader(prog, gl_fragshader))
|
||||
// then link the program (basically the linking stage of the program)
|
||||
GL(glLinkProgram(prog))
|
||||
if (shader_link_error(prog))
|
||||
REN_RET(0, REN_LINK)
|
||||
|
||||
// after linking the shaders can be detached and the source freed from
|
||||
// memory since the program is ready to use
|
||||
GL(glDetachShader(prog, gl_vertshader))
|
||||
GL(glDetachShader(prog, gl_fragshader))
|
||||
|
||||
return prog;
|
||||
}
|
||||
|
||||
|
||||
static void set_texture_parameters(GLuint type, GLuint wrap_s, GLuint wrap_t, GLuint upscale, GLuint downscale)
|
||||
{
|
||||
GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_s))
|
||||
GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_t))
|
||||
GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, upscale))
|
||||
GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, downscale))
|
||||
}
|
||||
|
||||
|
||||
static GLuint ren_texturergb_2d(const char *buf, int w, int h, int upscale, int downscale)
|
||||
{
|
||||
GLuint t;
|
||||
|
||||
if (!buf || w <= 0 || h <= 0)
|
||||
REN_RET(0, REN_INVAL)
|
||||
|
||||
GL(glGenTextures(1, &t))
|
||||
if (!t) REN_RET(0, REN_TEXTURE)
|
||||
|
||||
GL(glBindTexture(GL_TEXTURE_2D, t))
|
||||
GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, buf))
|
||||
|
||||
set_texture_parameters(GL_TEXTURE_2D, GL_REPEAT, GL_REPEAT, upscale, downscale);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
static GLuint ren_texturergba_2d(const char *buf, int w, int h, int upscale, int downscale)
|
||||
{
|
||||
GLuint t;
|
||||
|
||||
if (!buf || w <= 0 || h <= 0)
|
||||
REN_RET(0, REN_INVAL)
|
||||
|
||||
GL(glGenTextures(1, &t))
|
||||
if (!t) REN_RET(0, REN_TEXTURE)
|
||||
|
||||
GL(glBindTexture(GL_TEXTURE_2D, t))
|
||||
GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf))
|
||||
set_texture_parameters(GL_TEXTURE_2D, GL_REPEAT, GL_REPEAT, upscale, downscale);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
static GLuint ren_texturer_2d(const char *buf, int w, int h, int upscale, int downscale)
|
||||
{
|
||||
GLuint t;
|
||||
|
||||
if (!buf || w <= 0 || h <= 0)
|
||||
REN_RET(0, REN_INVAL)
|
||||
|
||||
GL(glGenTextures(1, &t))
|
||||
if (!t) REN_RET(0, REN_TEXTURE)
|
||||
|
||||
GL(glBindTexture(GL_TEXTURE_2D, t))
|
||||
GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, buf))
|
||||
set_texture_parameters(GL_TEXTURE_2D, GL_REPEAT, GL_REPEAT, upscale, downscale);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
static GLuint ren_texturer_rect(const char *buf, int w, int h, int upscale, int downscale)
|
||||
{
|
||||
GLuint t;
|
||||
|
||||
if (!buf || w <= 0 || h <= 0)
|
||||
REN_RET(0, REN_INVAL)
|
||||
|
||||
GL(glGenTextures(1, &t))
|
||||
if (!t) REN_RET(0, REN_TEXTURE)
|
||||
|
||||
GL(glBindTexture(GL_TEXTURE_RECTANGLE, t))
|
||||
GL(glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, buf))
|
||||
// a limitation of recatngle textures is that the wrapping mode is limited
|
||||
// to either clamp-to-edge or clamp-to-border
|
||||
set_texture_parameters(GL_TEXTURE_RECTANGLE, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, upscale, downscale);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
// FIXME: update only the newly generated character instead of the whole texture
|
||||
static int update_font_texture(int idx)
|
||||
{
|
||||
GL(glUseProgram(ren.font_prog))
|
||||
GL(glTexSubImage2D(
|
||||
GL_TEXTURE_RECTANGLE,
|
||||
0, 0, 0,
|
||||
ren.fonts[idx].font->width,
|
||||
ren.fonts[idx].font->height,
|
||||
GL_RED,
|
||||
GL_UNSIGNED_BYTE,
|
||||
ren.fonts[idx].font->atlas))
|
||||
//font_dump(ren.fonts[idx].font, "./atlas.png");
|
||||
GL(glUseProgram(0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// loads a font and returns it's index in the fonts array
|
||||
static int ren_load_font(int size, const char *path)
|
||||
{
|
||||
int idx = ren.fonts_no;
|
||||
struct font_atlas *f;
|
||||
ren.fonts = erealloc(ren.fonts, sizeof(struct ren_font)*(idx+1));
|
||||
ren.fonts[idx].font = font_init();
|
||||
f = ren.fonts[idx].font;
|
||||
if (!f)
|
||||
REN_RET(-1, REN_FONT)
|
||||
|
||||
if (!path)
|
||||
path = DEFAULT_FONT;
|
||||
|
||||
if (font_load(f, path, size))
|
||||
REN_RET(-1, REN_FONT)
|
||||
//font_dump(f, "./atlas.png");
|
||||
|
||||
// load font texture (atlas)
|
||||
ren.fonts[idx].texture = ren_texturer_rect(
|
||||
(const char *)f->atlas,
|
||||
f->width,
|
||||
f->height,
|
||||
GL_LINEAR, GL_LINEAR);
|
||||
if (!ren.fonts[idx].texture)
|
||||
return -1;
|
||||
|
||||
ren.fonts_no = idx+1;
|
||||
return idx;
|
||||
}
|
||||
|
||||
|
||||
// returns the index to the ren.fonts array to the font with the correct size
|
||||
// return -1 on errror
|
||||
static int ren_get_font(int size)
|
||||
{
|
||||
for (int i = 0; i < ren.fonts_no; i++) {
|
||||
if (ren.fonts[i].font->size == size)
|
||||
return i;
|
||||
}
|
||||
// TODO: add a way to change font family
|
||||
return ren_load_font(size, NULL);
|
||||
}
|
||||
|
||||
|
||||
int ren_init(SDL_Window *w)
|
||||
{
|
||||
// Initialize OpenGL
|
||||
if (!w)
|
||||
REN_RET(-1, REN_INVAL)
|
||||
// using version 3 does not allow to use glVertexAttribPointer() without
|
||||
// vertex buffer objects, so use compatibility mode
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
ren.gl = SDL_GL_CreateContext(w);
|
||||
if (!ren.gl) {
|
||||
printf("SDL: %s\n", SDL_GetError());
|
||||
REN_RET(-1, REN_CONTEXT)
|
||||
}
|
||||
|
||||
GL(glEnable(GL_BLEND))
|
||||
GL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA))
|
||||
GL(glDisable(GL_CULL_FACE))
|
||||
GL(glDisable(GL_DEPTH_TEST))
|
||||
GL(glEnable(GL_SCISSOR_TEST))
|
||||
GL(glEnable(GL_TEXTURE_2D))
|
||||
GL(glEnable(GL_TEXTURE_RECTANGLE))
|
||||
|
||||
GLenum glew_err = glewInit();
|
||||
if (glew_err != GLEW_OK)
|
||||
REN_RET(glew_err, REN_GLEW);
|
||||
|
||||
// Create stacks
|
||||
ren.font_stack = vtstack_init();
|
||||
ren.box_stack = vcstack_init();
|
||||
// generate the font buffer object
|
||||
GL(glGenBuffers(1, &ren.font_buffer))
|
||||
if (!ren.font_buffer) REN_RET(-1, REN_BUFFER)
|
||||
GL(glGenBuffers(1, &ren.box_buffer))
|
||||
if (!ren.box_buffer) REN_RET(-1, REN_BUFFER)
|
||||
|
||||
// Compile font shaders
|
||||
ren.font_prog = ren_compile_program(FONT_VERSHADER, FONT_FRAGSHADER);
|
||||
if (!ren.font_prog) return -1;
|
||||
// create the uniforms, if the returned value is -1 then the uniform may have
|
||||
// been optimized away, in any case do not return, just give a warning
|
||||
ren.viewsize_loc = GL(glGetUniformLocation(ren.font_prog, "viewsize"))
|
||||
if (ren.viewsize_loc == -1)
|
||||
printf("uniform %s was optimized away\n", "viewsize");
|
||||
ren.texturesize_loc = GL(glGetUniformLocation(ren.font_prog, "texturesize"))
|
||||
if (ren.texturesize_loc == -1)
|
||||
printf("uniform %s was optimized away\n", "texturesize");
|
||||
|
||||
// Compile box shaders
|
||||
ren.box_prog = ren_compile_program(BOX_VERSHADER, BOX_FRAGSHADER);
|
||||
if (!ren.box_prog) return -1;
|
||||
ren.box_viewsize_loc = GL(glGetUniformLocation(ren.box_prog, "viewsize"))
|
||||
if (ren.box_viewsize_loc == -1)
|
||||
printf("uniform %s was optimized away\n", "viewsize");
|
||||
|
||||
// Finishing touches
|
||||
ren.tabsize = REN_TABSIZE;
|
||||
int width, height;
|
||||
SDL_GetWindowSize(w, &width, &height);
|
||||
ren_update_viewport(width, height);
|
||||
GL(glClearColor(0.3f, 0.3f, 0.3f, 0.f))
|
||||
GL(glClear(GL_COLOR_BUFFER_BIT))
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int ren_clear(void)
|
||||
{
|
||||
GL(glScissor(0, 0, ren.width, ren.height))
|
||||
GL(glClear(GL_COLOR_BUFFER_BIT));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// idx refers to the fonts array index, this means that when drawing the font stack
|
||||
// only one font size at the time can be used
|
||||
static int ren_draw_font_stack(int idx)
|
||||
{
|
||||
struct font_atlas *font = ren.fonts[idx].font;
|
||||
GLuint font_texture = ren.fonts[idx].texture;
|
||||
|
||||
GL(glUseProgram(ren.font_prog))
|
||||
GL(glBindTexture(GL_TEXTURE_RECTANGLE, font_texture))
|
||||
|
||||
GL(glViewport(0, 0, ren.width, ren.height))
|
||||
// this has caused me some trouble, convert from image coordiates to viewport
|
||||
GL(glScissor(ren.s_x, ren.height-ren.s_y-ren.s_h, ren.s_w, ren.s_h))
|
||||
GL(glUniform2i(ren.viewsize_loc, ren.width, ren.height))
|
||||
GL(glUniform2i(ren.texturesize_loc, font->width, font->height))
|
||||
|
||||
GL(glBindBuffer(GL_ARRAY_BUFFER, ren.font_buffer))
|
||||
if (vtstack_changed(&ren.font_stack)) {
|
||||
if (vtstack_size_changed(&ren.font_stack)) {
|
||||
GL(glBufferData(
|
||||
GL_ARRAY_BUFFER,
|
||||
ren.font_stack.idx*sizeof(struct v_text),
|
||||
ren.font_stack.items,
|
||||
GL_DYNAMIC_DRAW))
|
||||
} else {
|
||||
GL(glBufferSubData(
|
||||
GL_ARRAY_BUFFER,
|
||||
0,
|
||||
ren.font_stack.idx*sizeof(struct v_text),
|
||||
ren.font_stack.items))
|
||||
}
|
||||
}
|
||||
// when passing ints to glVertexAttribPointer they are automatically
|
||||
// converted to floats
|
||||
GL(glVertexAttribPointer(
|
||||
REN_VERTEX_IDX,
|
||||
2,
|
||||
GL_INT,
|
||||
GL_FALSE,
|
||||
sizeof(struct v_text),
|
||||
0))
|
||||
GL(glVertexAttribPointer(
|
||||
REN_UV_IDX,
|
||||
2,
|
||||
GL_INT,
|
||||
GL_FALSE,
|
||||
sizeof(struct v_text),
|
||||
(void*)sizeof(vec2_i)))
|
||||
GL(glEnableVertexAttribArray(REN_VERTEX_IDX))
|
||||
GL(glEnableVertexAttribArray(REN_UV_IDX))
|
||||
|
||||
GL(glDrawArrays(GL_TRIANGLES, 0, ren.font_stack.idx))
|
||||
|
||||
GL(glDisableVertexAttribArray(REN_VERTEX_IDX))
|
||||
GL(glDisableVertexAttribArray(REN_UV_IDX))
|
||||
GL(glBindBuffer(GL_ARRAY_BUFFER, 0))
|
||||
GL(glBindTexture(GL_TEXTURE_RECTANGLE, 0))
|
||||
GL(glUseProgram(0))
|
||||
|
||||
vtstack_clear(&ren.font_stack);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int ren_draw_box_stack(void)
|
||||
{
|
||||
GL(glUseProgram(ren.box_prog))
|
||||
|
||||
GL(glViewport(0, 0, ren.width, ren.height))
|
||||
GL(glScissor(0, 0, ren.width, ren.height))
|
||||
GL(glUniform2i(ren.box_viewsize_loc, ren.width, ren.height))
|
||||
|
||||
GL(glBindBuffer(GL_ARRAY_BUFFER, ren.box_buffer))
|
||||
if(vcstack_changed(&ren.box_stack)) {
|
||||
if (vcstack_size_changed(&ren.box_stack)) {
|
||||
GL(glBufferData(
|
||||
GL_ARRAY_BUFFER,
|
||||
ren.box_stack.idx*sizeof(struct v_col),
|
||||
ren.box_stack.items,
|
||||
GL_DYNAMIC_DRAW))
|
||||
} else {
|
||||
GL(glBufferSubData(
|
||||
GL_ARRAY_BUFFER,
|
||||
0,
|
||||
ren.box_stack.idx*sizeof(struct v_col),
|
||||
ren.box_stack.items))
|
||||
}
|
||||
}
|
||||
// when passing ints to glVertexAttribPointer they are automatically
|
||||
// converted to floats
|
||||
GL(glVertexAttribPointer(
|
||||
REN_VERTEX_IDX,
|
||||
2,
|
||||
GL_INT,
|
||||
GL_FALSE,
|
||||
sizeof(struct v_col),
|
||||
0))
|
||||
// the color gets normalized
|
||||
GL(glVertexAttribPointer(
|
||||
REN_COLOR_IDX,
|
||||
4,
|
||||
GL_INT,
|
||||
GL_TRUE,
|
||||
sizeof(struct v_col),
|
||||
(void*)sizeof(vec2_i)))
|
||||
GL(glEnableVertexAttribArray(REN_VERTEX_IDX))
|
||||
GL(glEnableVertexAttribArray(REN_COLOR_IDX))
|
||||
|
||||
GL(glDrawArrays(GL_TRIANGLES, 0, ren.box_stack.idx))
|
||||
|
||||
GL(glDisableVertexAttribArray(REN_VERTEX_IDX))
|
||||
GL(glDisableVertexAttribArray(REN_COLOR_IDX))
|
||||
GL(glBindBuffer(GL_ARRAY_BUFFER, 0))
|
||||
GL(glUseProgram(0))
|
||||
|
||||
vcstack_clear(&ren.box_stack);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int ren_update_viewport(int w, int h)
|
||||
{
|
||||
ren.width = w;
|
||||
ren.height = h;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int ren_set_scissor(int x, int y, int w, int h)
|
||||
{
|
||||
ren.s_x = x;
|
||||
ren.s_y = y;
|
||||
ren.s_w = w;
|
||||
ren.s_h = h;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int ren_push_glyph(const struct font_glyph *g, int gx, int gy)
|
||||
{
|
||||
/* x4,y4 x3,y3
|
||||
* o-------------+
|
||||
* |(x,y) /|
|
||||
* | / |
|
||||
* | 2 / |
|
||||
* | / |
|
||||
* | / |
|
||||
* | / 1 |
|
||||
* |/ |
|
||||
* +-------------+
|
||||
* x1,y1 x2,y2 */
|
||||
struct v_text v;
|
||||
struct font_glyph c;
|
||||
c = *g;
|
||||
//printf("g: u=%d v=%d w=%d h=%d a=%d x=%d y=%d\n", c.u, c.v, c.w, c.h, c.a, c.x, c.y);
|
||||
//printf("v: x=%d y=%d u=%d v=%d\n", v.pos.x, v.pos.y, v.uv.u, v.uv.v);
|
||||
// x1,y1
|
||||
v = (struct v_text){
|
||||
.pos = { .x = gx+c.x, .y = gy+c.y+c.h },
|
||||
.uv = { .u = c.u, .v = c.v+c.h },
|
||||
};
|
||||
vtstack_push(&ren.font_stack, &v);
|
||||
// x2,y2
|
||||
v = (struct v_text){
|
||||
.pos = { .x = gx+c.x+c.w, .y = gy+c.y+c.h },
|
||||
.uv = { .u = c.u+c.w, .v = c.v+c.h },
|
||||
};
|
||||
vtstack_push(&ren.font_stack, &v);
|
||||
// x3,y3
|
||||
v = (struct v_text){
|
||||
.pos = { .x = gx+c.x+c.w, .y = gy+c.y },
|
||||
.uv = { .u = c.u+c.w, .v = c.v },
|
||||
};
|
||||
vtstack_push(&ren.font_stack, &v);
|
||||
// x1,y1
|
||||
v = (struct v_text){
|
||||
.pos = { .x = gx+c.x, .y = gy+c.y+c.h },
|
||||
.uv = { .u = c.u, .v = c.v+c.h },
|
||||
};
|
||||
vtstack_push(&ren.font_stack, &v);
|
||||
// x3,y3
|
||||
v = (struct v_text){
|
||||
.pos = { .x = gx+c.x+c.w, .y = gy+c.y },
|
||||
.uv = { .u = c.u+c.w, .v = c.v },
|
||||
};
|
||||
vtstack_push(&ren.font_stack, &v);
|
||||
// x4,y4
|
||||
v = (struct v_text){
|
||||
.pos = { .x = gx+c.x, .y = gy+c.y },
|
||||
.uv = { .u = c.u, .v = c.v },
|
||||
};
|
||||
vtstack_push(&ren.font_stack, &v);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static const struct font_glyph * get_glyph(unsigned int code, int idx)
|
||||
{
|
||||
const struct font_glyph *g;
|
||||
int updated;
|
||||
g = font_get_glyph_texture(ren.fonts[idx].font, code, &updated);
|
||||
if (!g)
|
||||
REN_RET(NULL, REN_FONT);
|
||||
if (updated) {
|
||||
if (update_font_texture(idx))
|
||||
REN_RET(NULL, REN_TEXTURE);
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
|
||||
// TODO: reduce repeating patterns in ren_get_text_box() and ren_render_text()
|
||||
int ren_get_text_box(const char *str, int *rw, int *rh, int size)
|
||||
{
|
||||
int w = 0, h = 0, x = 0, y = 0;
|
||||
const struct font_glyph *g;
|
||||
size_t off, ret;
|
||||
uint32_t cp;
|
||||
int idx = ren_get_font(size);
|
||||
if (idx < 0)
|
||||
return -1;
|
||||
|
||||
h = y = ren.fonts[idx].font->glyph_max_h;
|
||||
for (off = 0; (ret = grapheme_decode_utf8(str+off, SIZE_MAX, &cp)) > 0 && cp != 0; off += ret) {
|
||||
if (iscntrl(cp)) goto skip_get;
|
||||
if (!(g = get_glyph(cp, idx)))
|
||||
return -1;
|
||||
|
||||
x += g->x + g->a;
|
||||
// FIXME: generalize this thing
|
||||
skip_get:
|
||||
switch (cp) {
|
||||
case '\t': {
|
||||
const struct font_glyph *sp = get_glyph(' ', idx);
|
||||
if (!sp) return -1;
|
||||
x += (sp->x + sp->a)*ren.tabsize;
|
||||
}
|
||||
break;
|
||||
case '\r':
|
||||
x = 0;
|
||||
break;
|
||||
case '\n':
|
||||
// TODO: encode and/or store line height
|
||||
y += ren.fonts[idx].font->glyph_max_h;
|
||||
x = 0;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (x > w) w = x;
|
||||
if (y > h) h = y;
|
||||
}
|
||||
|
||||
if (rw) *rw = w;
|
||||
if (rh) *rh = h;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int ren_render_text(const char *str, int x, int y, int w, int h, int size)
|
||||
{
|
||||
const struct font_glyph *g;
|
||||
size_t ret, off;
|
||||
uint32_t cp;
|
||||
int gx = x, gy = y;
|
||||
int idx = ren_get_font(size);
|
||||
if (idx < 0)
|
||||
return -1;
|
||||
for (off = 0; (ret = grapheme_decode_utf8(str+off, SIZE_MAX, &cp)) > 0 && cp != 0; off += ret) {
|
||||
// skip special characters that render a box (not present in font)
|
||||
if (iscntrl(cp)) goto skip_render;
|
||||
if (!(g = get_glyph(cp, idx)))
|
||||
return -1;
|
||||
|
||||
// only push the glyph if it is inside the bounding box
|
||||
if (gx <= x+w && gy <= y+h)
|
||||
ren_push_glyph(g, gx, gy);
|
||||
// TODO: possible kerning needs to be applied here
|
||||
// TODO: handle other unicode control characters such as the
|
||||
// right-to-left isolate (\u2067)
|
||||
gx += g->x + g->a;
|
||||
skip_render:
|
||||
switch (cp) {
|
||||
case '\t': {
|
||||
const struct font_glyph *sp = get_glyph(' ', idx);
|
||||
if (!sp) return -1;
|
||||
gx += (sp->x + sp->a)*ren.tabsize;
|
||||
}
|
||||
break;
|
||||
case '\r':
|
||||
gx = x;
|
||||
break;
|
||||
case '\n':
|
||||
gy += ren.fonts[idx].font->glyph_max_h;
|
||||
gx = x;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
ren_set_scissor(x, y, w, h);
|
||||
ren_draw_font_stack(idx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// re-normalize color from 0-255 to 0-0x7fffffff, technically i'm dividing by 256 here
|
||||
#define RENORM(x) (((unsigned long long)(x)*0x7fffffff)>>8)
|
||||
#define R(x) (x&0xff)
|
||||
#define G(x) ((x>>8)&0xff)
|
||||
#define B(x) ((x>>16)&0xff)
|
||||
#define A(x) ((x>>24)&0xff)
|
||||
static int ren_push_box(int x, int y, int w, int h, unsigned int color)
|
||||
{
|
||||
/* x4,y4 x3,y3
|
||||
* o-------------+
|
||||
* |(x,y) /|
|
||||
* | / |
|
||||
* | 2 / |
|
||||
* | / |
|
||||
* | / |
|
||||
* | / 1 |
|
||||
* |/ |
|
||||
* +-------------+
|
||||
* x1,y1 x2,y2 */
|
||||
struct v_col v;
|
||||
vec4_i c = {
|
||||
.r = RENORM(R(color)),
|
||||
.g = RENORM(G(color)),
|
||||
.b = RENORM(B(color)),
|
||||
.a = RENORM(A(color)),
|
||||
};
|
||||
// x1, y1
|
||||
v = (struct v_col){ .pos = { .x = x, .y = y+h }, .col = c };
|
||||
vcstack_push(&ren.box_stack, &v);
|
||||
// x2, y2
|
||||
v = (struct v_col){ .pos = { .x = x+w, .y = y+h }, .col = c };
|
||||
vcstack_push(&ren.box_stack, &v);
|
||||
// x3, y3
|
||||
v = (struct v_col){ .pos = { .x = x+w, .y = y }, .col = c };
|
||||
vcstack_push(&ren.box_stack, &v);
|
||||
// x1, y1
|
||||
v = (struct v_col){ .pos = { .x = x, .y = y+h }, .col = c };
|
||||
vcstack_push(&ren.box_stack, &v);
|
||||
// x3, y3
|
||||
v = (struct v_col){ .pos = { .x = x+w, .y = y }, .col = c };
|
||||
vcstack_push(&ren.box_stack, &v);
|
||||
// x4, y4
|
||||
v = (struct v_col){ .pos = { .x = x, .y = y }, .col = c };
|
||||
vcstack_push(&ren.box_stack, &v);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int ren_render_box(int x, int y, int w, int h, unsigned int color)
|
||||
{
|
||||
ren_push_box(x, y, w, h, color);
|
||||
ren_draw_box_stack();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int ren_free(void)
|
||||
{
|
||||
GL(glUseProgram(0))
|
||||
GL(glBindBuffer(GL_ARRAY_BUFFER, 0))
|
||||
GL(glDeleteProgram(ren.box_prog));
|
||||
GL(glDeleteProgram(ren.font_prog));
|
||||
GL(glDeleteBuffers(1, &ren.font_buffer))
|
||||
for (int i = 0; i < ren.fonts_no; i++) {
|
||||
GL(glDeleteTextures(1, &ren.fonts[i].texture))
|
||||
font_free(ren.fonts[i].font);
|
||||
}
|
||||
SDL_GL_DeleteContext(ren.gl);
|
||||
vtstack_free(&ren.font_stack);
|
||||
vcstack_free(&ren.box_stack);
|
||||
return 0;
|
||||
}
|
||||
@ -1,54 +0,0 @@
|
||||
#ifndef _RENDERER_H
|
||||
#define _RENDERER_H
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
|
||||
#define DEFAULT_FONT "/usr/share/fonts/TTF/FiraCode-Regular.ttf"
|
||||
#define FONT_VERSHADER "./font_vertshader.glsl"
|
||||
#define FONT_FRAGSHADER "./font_fragshader.glsl"
|
||||
#define BOX_VERSHADER "./box_vertshader.glsl"
|
||||
#define BOX_FRAGSHADER "./box_fragshader.glsl"
|
||||
#define REN_VERTEX_IDX 0
|
||||
#define REN_UV_IDX 1
|
||||
#define REN_COLOR_IDX 2
|
||||
#define REN_TABSIZE 8
|
||||
|
||||
|
||||
typedef struct {
|
||||
union { int x, u; };
|
||||
union { int y, v; };
|
||||
} vec2_i;
|
||||
|
||||
typedef struct {
|
||||
union { int x, r; };
|
||||
union { int y, g; };
|
||||
union { int z, b; };
|
||||
union { int w, a; };
|
||||
} vec4_i;
|
||||
|
||||
// textured vertex
|
||||
struct v_text {
|
||||
vec2_i pos;
|
||||
vec2_i uv;
|
||||
};
|
||||
|
||||
// colored vertex
|
||||
struct v_col {
|
||||
vec2_i pos;
|
||||
vec4_i col;
|
||||
};
|
||||
|
||||
|
||||
int ren_init(SDL_Window *sdl_window);
|
||||
int ren_free(void);
|
||||
const char * ren_strerror(void);
|
||||
int ren_update_viewport(int w, int h);
|
||||
int ren_set_scissor(int x, int y, int w, int h);
|
||||
int ren_get_text_box(const char *str, int *rw, int *rh, int size);
|
||||
int ren_render_text(const char *str, int x, int y, int w, int h, int size);
|
||||
int ren_render_box(int x, int y, int w, int h, unsigned int color);
|
||||
int ren_clear(void);
|
||||
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,126 +0,0 @@
|
||||
#define _POSIX_C_SOURCE 200809l
|
||||
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <err.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
|
||||
const char *bit_rep[16] = {
|
||||
[ 0] = "0000", [ 1] = "0001", [ 2] = "0010", [ 3] = "0011",
|
||||
[ 4] = "0100", [ 5] = "0101", [ 6] = "0110", [ 7] = "0111",
|
||||
[ 8] = "1000", [ 9] = "1001", [10] = "1010", [11] = "1011",
|
||||
[12] = "1100", [13] = "1101", [14] = "1110", [15] = "1111",
|
||||
};
|
||||
|
||||
|
||||
|
||||
void * emalloc(unsigned long int size)
|
||||
{
|
||||
void *r = malloc(size);
|
||||
if (!r)
|
||||
err(EXIT_FAILURE, "malloc() of size %ld", size);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
void * ecalloc(unsigned long int nmemb, unsigned long int size)
|
||||
{
|
||||
void *r = calloc(nmemb, size);
|
||||
if (!r)
|
||||
err(EXIT_FAILURE, "calloc() of size %ld, nmemb %ld", size, nmemb);
|
||||
return r;
|
||||
}
|
||||
|
||||
void * erealloc(void *ptr, unsigned long int size)
|
||||
{
|
||||
void *r = realloc(ptr, size);
|
||||
if (!r)
|
||||
err(EXIT_FAILURE, "ralloc() of 0x%lx, to size %ld", (unsigned long)ptr, size);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
void efree(void *ptr)
|
||||
{
|
||||
if (ptr)
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
|
||||
void map_file(const unsigned char **str, int *size, const char *path)
|
||||
{
|
||||
if (!path) {
|
||||
errno = EINVAL;
|
||||
err(EXIT_FAILURE, "NULL filename");
|
||||
}
|
||||
FILE *fp = fopen(path, "r");
|
||||
if (!fp)
|
||||
err(EXIT_FAILURE, "Cannot open file %s", path);
|
||||
*size = lseek(fileno(fp), 0, SEEK_END);
|
||||
if (*size == (off_t)-1)
|
||||
err(EXIT_FAILURE, "lseek() failed");
|
||||
*str = mmap(0, *size, PROT_READ, MAP_PRIVATE, fileno(fp), 0);
|
||||
if (*str == (void*)-1)
|
||||
err(EXIT_FAILURE, "mmap() failed");
|
||||
if (fclose(fp))
|
||||
err(EXIT_FAILURE, "Error closing file");
|
||||
}
|
||||
|
||||
|
||||
void dump_file(const char *path, char **buf, int *buf_len)
|
||||
{
|
||||
if (!path) {
|
||||
errno = EINVAL;
|
||||
err(EXIT_FAILURE, "NULL filename");
|
||||
}
|
||||
if (!buf) {
|
||||
errno = EINVAL;
|
||||
err(EXIT_FAILURE, "No buffer specified");
|
||||
}
|
||||
int m = 0;
|
||||
if (!buf_len)
|
||||
buf_len = &m;
|
||||
FILE *fp = fopen(path, "r");
|
||||
if (!fp)
|
||||
err(EXIT_FAILURE, "Cannot open file %s", path);
|
||||
*buf_len = lseek(fileno(fp), 0, SEEK_END);
|
||||
rewind(fp);
|
||||
if (*buf_len == (off_t)-1)
|
||||
err(EXIT_FAILURE, "lseek() failed");
|
||||
*buf = emalloc(*buf_len+1);
|
||||
memset(*buf, 0, *buf_len+1);
|
||||
int ret = fread(*buf, 1, *buf_len, fp);
|
||||
if (ret != *buf_len)
|
||||
err(EXIT_FAILURE, "fread() returned short %s", ferror(fp) ? "stream error" : feof(fp) ? "EOF reached" : "unknown error");
|
||||
if (fclose(fp))
|
||||
err(EXIT_FAILURE, "Error closing file");
|
||||
}
|
||||
|
||||
|
||||
void print_byte(unsigned char byte)
|
||||
{
|
||||
printf("%s%s", bit_rep[byte >> 4], bit_rep[byte & 0x0F]);
|
||||
}
|
||||
|
||||
|
||||
static struct timespec clock_start, clock_stop;
|
||||
|
||||
void stopwatch_start(void)
|
||||
{
|
||||
clock_gettime(CLOCK_MONOTONIC_COARSE, &clock_start);
|
||||
}
|
||||
|
||||
|
||||
double stopwatch_get(void)
|
||||
{
|
||||
clock_gettime(CLOCK_MONOTONIC_COARSE, &clock_stop);
|
||||
return (clock_stop.tv_sec-clock_start.tv_sec)+(double)(clock_stop.tv_nsec-clock_start.tv_nsec)/(double)1000000000L;
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
#ifndef _UTIL_H
|
||||
#define _UTIL_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
void * emalloc(unsigned long int size);
|
||||
void * ecalloc(unsigned long int nmemb, unsigned long int size);
|
||||
void * erealloc(void *ptr, unsigned long int size);
|
||||
void efree(void *ptr);
|
||||
|
||||
void map_file(const unsigned char **str, int *size, const char *path);
|
||||
void dump_file(const char *path, char **buf, int *buf_len);
|
||||
|
||||
void print_byte(unsigned char byte);
|
||||
|
||||
void stopwatch_start(void);
|
||||
double stopwatch_get(void);
|
||||
|
||||
#define TIME_SEC(f) \
|
||||
{ \
|
||||
stopwatch_start(); \
|
||||
f; \
|
||||
printf("\"%s\" took %f seconds", #f, stopwatch_get()); \
|
||||
}
|
||||
|
||||
#define TIME_MS(f) \
|
||||
{ \
|
||||
stopwatch_start(); \
|
||||
f; \
|
||||
printf("\"%s\" took %f ms", #f, stopwatch_get()*1000.0f); \
|
||||
}
|
||||
|
||||
#endif
|
||||
285
ugui.h
285
ugui.h
@ -1,285 +0,0 @@
|
||||
#ifndef _UG_HEADER
|
||||
#define _UG_HEADER
|
||||
|
||||
#define UG_STACK(T) struct { T *items; int idx, size, sorted; }
|
||||
#define BIT(n) (1 << n)
|
||||
#define RGBA_FORMAT(x) { .a=x&0xff, .b=(x>>8)&0xff, .g=(x>>16)&0xff, .r=(x>>24)&0xff }
|
||||
#define RGB_FORMAT(x) { .a=0xff, .b=x&0xff, .g=(x>>8)&0xff, .r=(x>>16)&0xff }
|
||||
#define SIZE_PX(x) { .size.i=x, .unit=UG_UNIT_PX }
|
||||
#define SIZE_MM(x) { .size.f=x, .unit=UG_UNIT_MM }
|
||||
#define SIZE_PT(x) { .size.f=x, .unit=UG_UNIT_PT }
|
||||
#define SQUARE(x) .w = x, .h = x
|
||||
|
||||
// basic types
|
||||
typedef unsigned int ug_id_t;
|
||||
typedef struct { union {int x, w;}; union {int y, h;}; } ug_vec2_t;
|
||||
typedef struct { unsigned char a, b, g, r; } ug_color_t;
|
||||
typedef struct { int x, y, w, h; } ug_rect_t;
|
||||
typedef struct { union {int i; float f;} size; int unit; } ug_size_t;
|
||||
// div has information about the phisical dimension
|
||||
typedef struct { ug_size_t x, y, w, h;} ug_div_t;
|
||||
|
||||
typedef enum {
|
||||
UG_UNIT_PX = 0,
|
||||
UG_UNIT_MM,
|
||||
UG_UNIT_PT,
|
||||
} ug_unit_t;
|
||||
|
||||
|
||||
// element type
|
||||
typedef struct {
|
||||
ug_id_t id;
|
||||
unsigned short int type;
|
||||
unsigned short int flags;
|
||||
ug_rect_t rect, rca;
|
||||
const char *name;
|
||||
union {
|
||||
struct {
|
||||
const char *txt;
|
||||
} btn;
|
||||
};
|
||||
} ug_element_t;
|
||||
|
||||
enum {
|
||||
UG_ELEM_BUTTON, // button
|
||||
UG_ELEM_TXTBTN, // textual button, a button but without frame
|
||||
UG_ELEM_CHECK, // checkbox
|
||||
UG_ELEM_RADIO, // radio button
|
||||
UG_ELEM_TOGGLE, // toggle button
|
||||
UG_ELEM_LABEL, // simple text
|
||||
UG_ELEM_UPDOWN, // text with two buttons up and down
|
||||
UG_ELEM_TEXTINPUT, // text input box
|
||||
UG_ELEM_TEXTBOX, // text surrounded by a box
|
||||
UG_ELEM_IMG, // image, icon
|
||||
UG_ELEM_SPACE, // takes up space
|
||||
};
|
||||
|
||||
enum {
|
||||
ELEM_CLIPPED = BIT(0),
|
||||
};
|
||||
|
||||
|
||||
// container type, a container is an entity that contains layouts, a container has
|
||||
// a haight a width and their maximum values, in essence a container is a rectangular
|
||||
// area that can be resized and sits somewhere on the drawable region
|
||||
// the z index of a container is determined by it's position on the stack
|
||||
typedef struct {
|
||||
ug_id_t id;
|
||||
const char *name;
|
||||
ug_rect_t rect;
|
||||
// absolute position rect
|
||||
ug_rect_t rca;
|
||||
unsigned int flags;
|
||||
// layouting and elements
|
||||
// total space used by elements, x and y are the starting coordinates
|
||||
// for elements
|
||||
ug_rect_t space;
|
||||
// origin for in-row and in-column elements
|
||||
ug_vec2_t c_orig, r_orig;
|
||||
UG_STACK(ug_element_t) elem_stack;
|
||||
ug_id_t selected_elem, hover_elem;
|
||||
} ug_container_t;
|
||||
|
||||
// the container flags
|
||||
enum {
|
||||
UG_CNT_FLOATING = BIT(0), // is on top of everything else
|
||||
UG_CNT_RESIZE_RIGHT = BIT(1), // can be resized from the right border
|
||||
UG_CNT_RESIZE_BOTTOM = BIT(2), // can be resized from the bottom border
|
||||
UG_CNT_RESIZE_LEFT = BIT(3), // can be resized from the left border
|
||||
UG_CNT_RESIZE_TOP = BIT(4), // can be resized from the top border
|
||||
UG_CNT_SCROLL_X = BIT(5), // can have horizontal scrolling
|
||||
UG_CNT_SCROLL_Y = BIT(6), // can have vertical scrolling
|
||||
UG_CNT_MOVABLE = BIT(7), // can be moved around
|
||||
// container state
|
||||
CNT_STATE_NONE = BIT(30),
|
||||
CNT_STATE_MOVING = BIT(29),
|
||||
CNT_STATE_RESIZE_T = BIT(28),
|
||||
CNT_STATE_RESIZE_B = BIT(27),
|
||||
CNT_STATE_RESIZE_L = BIT(26),
|
||||
CNT_STATE_RESIZE_R = BIT(25),
|
||||
CNT_STATE_RESIZE_D = BIT(24),
|
||||
CNT_STATE_DELETE = BIT(23), // The container is marked for removal
|
||||
// layouting
|
||||
CNT_LAYOUT_COLUMN = BIT(22),
|
||||
};
|
||||
|
||||
// style, defines default height, width, color, margins, borders, etc
|
||||
// all dimensions should be taken as a reference when doing the layout since
|
||||
// ultimately it's the layout that decides them. For example when deciding how to
|
||||
// allocate space one can say that the default size of a region that allocates a
|
||||
// slider has the style's default dimensions for a slider
|
||||
typedef struct {
|
||||
struct { ug_color_t bg, fg; } color;
|
||||
ug_size_t margin;
|
||||
struct {
|
||||
ug_color_t color;
|
||||
ug_size_t size;
|
||||
} border;
|
||||
struct {
|
||||
struct { ug_color_t bg, fg; } color;
|
||||
ug_size_t height, font_size;
|
||||
} title;
|
||||
struct {
|
||||
struct { ug_color_t act, bg, fg, sel, br; } color;
|
||||
ug_size_t font_size, border;
|
||||
} btn;
|
||||
} ug_style_t;
|
||||
|
||||
|
||||
// render commands
|
||||
struct ug_cmd_rect { int x, y, w, h; ug_color_t color; };
|
||||
struct ug_cmd_text { int x, y, size; ug_color_t color; const char *str; };
|
||||
|
||||
typedef struct {
|
||||
unsigned int type;
|
||||
union {
|
||||
struct ug_cmd_rect rect;
|
||||
struct ug_cmd_text text;
|
||||
};
|
||||
} ug_cmd_t;
|
||||
|
||||
typedef enum {
|
||||
UG_CMD_NULL = 0,
|
||||
UG_CMD_RECT,
|
||||
UG_CMD_TEXT,
|
||||
} ug_cmd_type_t;
|
||||
|
||||
// window side
|
||||
enum {
|
||||
UG_SIDE_TOP = 0,
|
||||
UG_SIDE_BOTTOM,
|
||||
UG_SIDE_LEFT,
|
||||
UG_SIDE_RIGHT,
|
||||
};
|
||||
|
||||
// mouse buttons
|
||||
enum {
|
||||
UG_BTN_LEFT = BIT(0),
|
||||
UG_BTN_MIDDLE = BIT(1),
|
||||
UG_BTN_RIGHT = BIT(2),
|
||||
UG_BTN_4 = BIT(3),
|
||||
UG_BTN_5 = BIT(4),
|
||||
};
|
||||
|
||||
// context
|
||||
typedef struct {
|
||||
// some style information
|
||||
const ug_style_t *style;
|
||||
// style_px is a style cache where all measurements are already in pixels
|
||||
const ug_style_t *style_px;
|
||||
// ppi: pixels per inch
|
||||
// ppm: pixels per millimeter
|
||||
// ppd: pixels per dot
|
||||
float ppi, ppm, ppd;
|
||||
float last_ppi, last_ppm, last_ppd;
|
||||
// containers need to know how big the "main container" is so that all
|
||||
// the relative positioning work
|
||||
ug_vec2_t size;
|
||||
ug_rect_t origin;
|
||||
// which context and element we are hovering
|
||||
ug_id_t hover_cnt;
|
||||
// active is updated on mousedown and released on mouseup
|
||||
// the id of the "active" element, active means different things for
|
||||
// different elements, for exaple active for a button means to be pressed,
|
||||
// and for a text box it means to be focused
|
||||
ug_id_t active_cnt, last_active_cnt;
|
||||
// id of the selected container, used for layout
|
||||
// NOTE: since the stacks can be relocated with realloc it is better not
|
||||
// to use a pointer here, even tough it would be better for efficiency
|
||||
ug_id_t selected_cnt;
|
||||
// count the frames for fun
|
||||
unsigned long int frame;
|
||||
// mouse data
|
||||
struct {
|
||||
ug_vec2_t pos;
|
||||
ug_vec2_t last_pos;
|
||||
ug_vec2_t delta;
|
||||
ug_vec2_t scroll_delta;
|
||||
// mouse.update: a mask of the mouse buttons that are being updated
|
||||
unsigned char update;
|
||||
// mouse.hold: a mask of the buttons that are being held
|
||||
unsigned char hold;
|
||||
} mouse;
|
||||
// keyboard key pressed
|
||||
struct {
|
||||
unsigned char update;
|
||||
unsigned char hold;
|
||||
} key;
|
||||
// input text buffer
|
||||
char input_text[32];
|
||||
// stacks
|
||||
UG_STACK(ug_container_t) cnt_stack;
|
||||
UG_STACK(ug_cmd_t) cmd_stack;
|
||||
// command stack iterator
|
||||
int cmd_it;
|
||||
} ug_ctx_t;
|
||||
|
||||
|
||||
// context initialization
|
||||
ug_ctx_t * ug_ctx_new(void);
|
||||
void ug_ctx_free(ug_ctx_t *ctx);
|
||||
// updates the context with user information
|
||||
// ppi: pixels per inch, the scale used for all em/mm to pixel calculations
|
||||
// scale: it is the scale between the physical pixels on the screen and the pixels
|
||||
// on the buffer, if unsure set to 1.0
|
||||
int ug_ctx_set_displayinfo(ug_ctx_t *ctx, float scale, float ppi);
|
||||
int ug_ctx_set_drawableregion(ug_ctx_t *ctx, ug_vec2_t size);
|
||||
int ug_ctx_set_style(ug_ctx_t *ctx, const ug_style_t *style);
|
||||
|
||||
|
||||
// define containers, name is used as a salt for the container id, all sizes are
|
||||
// the default ones, resizing is done automagically with internal state
|
||||
|
||||
// a floating container can be placed anywhere and can be resized, acts like a
|
||||
// window inside another window
|
||||
int ug_container_floating(ug_ctx_t *ctx, const char *name, ug_div_t div);
|
||||
// like a floating container but cannot be resized
|
||||
int ug_container_popup(ug_ctx_t *ctx, const char *name, ug_div_t div);
|
||||
// a menu bar is a container of fixed height, cannot be resized and sits at the
|
||||
// top of the window
|
||||
int ug_container_menu_bar(ug_ctx_t *ctx, const char *name, ug_size_t height);
|
||||
// a sidebar is a variable size container anchored to one side of the window
|
||||
int ug_container_sidebar(ug_ctx_t *ctx, const char *name, ug_size_t size, int side);
|
||||
// a body is a container that scales with the window, sits at it's center and cannot
|
||||
// be resized, it also fills all the available space
|
||||
int ug_container_body(ug_ctx_t *ctx, const char *name);
|
||||
// mark a conatiner for removal, it will be freed at the next frame beginning if
|
||||
// name is NULL then use the selected container
|
||||
int ug_container_remove(ug_ctx_t *ctx, const char *name);
|
||||
// get the drawable area of the container, if name is NULL then use the selected
|
||||
// container
|
||||
ug_rect_t ug_container_get_rect(ug_ctx_t *ctx, const char *name);
|
||||
|
||||
// layouting, the following functions define how different ui elements are placed
|
||||
// inside the selected container. A new element is aligned respective to the
|
||||
// previous element and/or to the container, particularly elements can be placed
|
||||
// in a row or a column.
|
||||
int ug_layout_row(ug_ctx_t *ctx);
|
||||
int ug_layout_column(ug_ctx_t *ctx);
|
||||
int ug_layout_next_row(ug_ctx_t *ctx);
|
||||
int ug_layout_next_column(ug_ctx_t *ctx);
|
||||
|
||||
// elements
|
||||
int ug_element_button(ug_ctx_t *ctx, const char *name, const char *txt, ug_div_t dim);
|
||||
int ug_element_textbtn(ug_ctx_t *ctx, const char *name, const char *txt, ug_div_t dim);
|
||||
|
||||
// Input functions
|
||||
int ug_input_mousemove(ug_ctx_t *ctx, int x, int y);
|
||||
int ug_input_mousedown(ug_ctx_t *ctx, unsigned int mask);
|
||||
int ug_input_mouseup(ug_ctx_t *ctx, unsigned int mask);
|
||||
int ug_input_scroll(ug_ctx_t *ctx, int x, int y);
|
||||
// TODO: other input functions
|
||||
|
||||
// Frame handling
|
||||
int ug_frame_begin(ug_ctx_t *ctx);
|
||||
int ug_frame_end(ug_ctx_t *ctx);
|
||||
|
||||
// Commands
|
||||
// get the next command, save iteration state inside 'iterator'
|
||||
ug_cmd_t * ug_cmd_next(ug_ctx_t *ctx);
|
||||
|
||||
|
||||
#undef UG_STACK
|
||||
#undef BIT
|
||||
|
||||
#endif
|
||||
Loading…
x
Reference in New Issue
Block a user