From 0796b2cbcdee4aa85ca81d53d03944be4f7ced1d Mon Sep 17 00:00:00 2001 From: martinfouilleul Date: Thu, 2 Feb 2023 11:40:22 +0100 Subject: [PATCH] Switched to fixed point in gles triangle rasterization, and fixed wrong offset curve check collapsing the internal control points --- examples/win_canvas/main.c | 19 +- rasterization_notes.txt | 9 + src/gles_canvas_shaders.h | 44 +- .../gles_canvas_fragment.glsl | 42 +- src/graphics.c | 9 +- todo.txt | 408 +++++++++--------- 6 files changed, 265 insertions(+), 266 deletions(-) create mode 100644 rasterization_notes.txt diff --git a/examples/win_canvas/main.c b/examples/win_canvas/main.c index 75cfbad..fdc2c32 100644 --- a/examples/win_canvas/main.c +++ b/examples/win_canvas/main.c @@ -41,7 +41,7 @@ int main() mp_window_bring_to_front(window); mp_window_focus(window); - f32 dx = 17.000029, dy = 0; + f32 dx = 0, dy = 0; while(!mp_should_quit()) { @@ -67,30 +67,27 @@ int main() case MP_EVENT_KEYBOARD_KEY: { - printf("key %i: %s\n", - event.key.code, - event.key.action == MP_KEY_PRESS ? "press" : (event.key.action == MP_KEY_RELEASE ? "release" : "repeat")); if(event.key.action == MP_KEY_PRESS || event.key.action == MP_KEY_REPEAT) { if(event.key.code == MP_KEY_LEFT) { printf("left\n"); - dx-=0.1; + dx-=1.1; } else if(event.key.code == MP_KEY_RIGHT) { printf("right\n"); - dx+=0.1; + dx+=1.1; } else if(event.key.code == MP_KEY_UP) { printf("up\n"); - dy+=0.1; + dy+=1.1; } else if(event.key.code == MP_KEY_DOWN) { printf("down\n"); - dy-=0.1; + dy-=1.1; } } } break; @@ -102,12 +99,10 @@ int main() mg_surface_prepare(surface); - printf("dx = %f, dy = %f\n", dx, dy); - // background mg_set_color_rgba(1, 0, 1, 1); mg_clear(); -/* + // head mg_set_color_rgba(1, 1, 0, 1); mg_circle_fill(dx+400, dy+300, 200); @@ -123,8 +118,6 @@ int main() // eyes mg_ellipse_fill(dx+330, dy+350, 30, 50); mg_ellipse_fill(dx+470, dy+350, 30, 50); -*/ - mg_rectangle_fill((int)(dx + 200), 200, (int)(dy+300), (int)(dy+300)); mg_flush(); mg_surface_present(surface); diff --git a/rasterization_notes.txt b/rasterization_notes.txt new file mode 100644 index 0000000..fecf924 --- /dev/null +++ b/rasterization_notes.txt @@ -0,0 +1,9 @@ +https://fgiesen.wordpress.com/2013/02/08/triangle-rasterization-in-practice/ + +https://github.com/rygorous/trirast/blob/master/main.cpp + +https://joshbeam.com/articles/triangle_rasterization/ + +https://nlguillemot.wordpress.com/2016/07/10/rasterizer-notes/ + +https://web.archive.org/web/20120625103536/http://devmaster.net/forums/topic/1145-advanced-rasterization/ diff --git a/src/gles_canvas_shaders.h b/src/gles_canvas_shaders.h index 93af0f0..a04c9b7 100644 --- a/src/gles_canvas_shaders.h +++ b/src/gles_canvas_shaders.h @@ -2,7 +2,7 @@ * * file: gles_canvas_shaders.h * note: string literals auto-generated by embed_text.py -* date: 01/022023 +* date: 02/022023 * **********************************************************************/ #ifndef __GLES_CANVAS_SHADERS_H__ @@ -36,32 +36,25 @@ const char* gles_canvas_fragment = "layout(location = 0) uniform int indexCount;\n" "layout(location = 0) out vec4 fragColor;\n" "\n" -"bool is_top_left(vec2 a, vec2 b)\n" +"bool is_top_left(ivec2 a, ivec2 b)\n" "{\n" " return( (a.y == b.y && b.x < a.x)\n" " ||(b.y < a.y));\n" "}\n" "\n" -"float orient2d(vec2 a, vec2 b, vec2 c)\n" +"int orient2d(ivec2 a, ivec2 b, ivec2 p)\n" "{\n" -" //////////////////////////////////////////////////////////////////////////////////////////\n" -" //TODO(martin): FIX this. This is a **horrible** quick hack to fix the precision issues\n" -" // arising when a, b, and c are close. But it degrades when a, c, and c\n" -" // are big. The proper solution is to change the expression to avoid\n" -" // precision loss but I'm too busy/lazy to do it now.\n" -" //////////////////////////////////////////////////////////////////////////////////////////\n" -" a *= 10.;\n" -" b *= 10.;\n" -" c *= 10.;\n" -" return((b.x-a.x)*(c.y-a.y) - (b.y-a.y)*(c.x-a.x));\n" +" return((b.x-a.x)*(p.y-a.y) - (b.y-a.y)*(p.x-a.x));\n" "}\n" "\n" "void main()\n" "{\n" +" float subPixelFactor = 16.;\n" +"\n" " vec4 pixelColor = vec4(0.0, 1.0, 0.0, 1.0);\n" " vec4 currentColor = vec4(0., 0., 0., 1.0);\n" "\n" -" vec2 samplePoint = gl_FragCoord.xy;\n" +" ivec2 samplePoint = ivec2(gl_FragCoord.xy * subPixelFactor + vec2(0.5, 0.5));\n" "\n" " int currentZIndex = -1;\n" " int flipCount = 0;\n" @@ -73,22 +66,22 @@ const char* gles_canvas_fragment = " uint i1 = indexBuffer.elements[i+1];\n" " uint i2 = indexBuffer.elements[i+2];\n" "\n" -" vec2 p0 = vertexBuffer.elements[i0].pos;\n" -" vec2 p1 = vertexBuffer.elements[i1].pos;\n" -" vec2 p2 = vertexBuffer.elements[i2].pos;\n" +" ivec2 p0 = ivec2(vertexBuffer.elements[i0].pos * subPixelFactor + vec2(0.5, 0.5));\n" +" ivec2 p1 = ivec2(vertexBuffer.elements[i1].pos * subPixelFactor + vec2(0.5, 0.5));\n" +" ivec2 p2 = ivec2(vertexBuffer.elements[i2].pos * subPixelFactor + vec2(0.5, 0.5));\n" "\n" " int zIndex = vertexBuffer.elements[i0].zIndex;\n" " vec4 color = vertexBuffer.elements[i0].color;\n" "\n" " //NOTE(martin): reorder triangle counter-clockwise and compute bias for each edge\n" -" float cw = (p1 - p0).x*(p2 - p0).y - (p1 - p0).y*(p2 - p0).x;\n" -" if(cw < 0.)\n" +" int cw = (p1 - p0).x*(p2 - p0).y - (p1 - p0).y*(p2 - p0).x;\n" +" if(cw < 0)\n" " {\n" " uint tmpIndex = i1;\n" " i1 = i2;\n" " i2 = tmpIndex;\n" "\n" -" vec2 tmpPoint = p1;\n" +" ivec2 tmpPoint = p1;\n" " p1 = p2;\n" " p2 = tmpPoint;\n" " }\n" @@ -101,14 +94,13 @@ const char* gles_canvas_fragment = " int bias1 = is_top_left(p2, p0) ? 0 : -1;\n" " int bias2 = is_top_left(p0, p1) ? 0 : -1;\n" "\n" -" float w0 = orient2d(p1, p2, samplePoint);\n" -" float w1 = orient2d(p2, p0, samplePoint);\n" -" float w2 = orient2d(p0, p1, samplePoint);\n" +" int w0 = orient2d(p1, p2, samplePoint);\n" +" int w1 = orient2d(p2, p0, samplePoint);\n" +" int w2 = orient2d(p0, p1, samplePoint);\n" "\n" -" if((int(w0)+bias0) >= 0 && (int(w1)+bias1) >= 0 && (int(w2)+bias2) >= 0)\n" +" if((w0+bias0) >= 0 && (w1+bias1) >= 0 && (w2+bias2) >= 0)\n" " {\n" -" //TODO check cubic\n" -" vec4 cubic = (cubic0*w0 + cubic1*w1 + cubic2*w2)/(w0+w1+w2);\n" +" vec4 cubic = (cubic0*float(w0) + cubic1*float(w1) + cubic2*float(w2))/(float(w0)+float(w1)+float(w2));\n" "\n" " float eps = 0.0001;\n" " if(cubic.w*(cubic.x*cubic.x*cubic.x - cubic.y*cubic.z) <= eps)\n" diff --git a/src/gles_canvas_shaders/gles_canvas_fragment.glsl b/src/gles_canvas_shaders/gles_canvas_fragment.glsl index dfaf319..a166b65 100644 --- a/src/gles_canvas_shaders/gles_canvas_fragment.glsl +++ b/src/gles_canvas_shaders/gles_canvas_fragment.glsl @@ -23,32 +23,25 @@ layout(binding = 1) buffer indexBufferSSBO { layout(location = 0) uniform int indexCount; layout(location = 0) out vec4 fragColor; -bool is_top_left(vec2 a, vec2 b) +bool is_top_left(ivec2 a, ivec2 b) { return( (a.y == b.y && b.x < a.x) ||(b.y < a.y)); } -float orient2d(vec2 a, vec2 b, vec2 c) +int orient2d(ivec2 a, ivec2 b, ivec2 p) { - ////////////////////////////////////////////////////////////////////////////////////////// - //TODO(martin): FIX this. This is a **horrible** quick hack to fix the precision issues - // arising when a, b, and c are close. But it degrades when a, c, and c - // are big. The proper solution is to change the expression to avoid - // precision loss but I'm too busy/lazy to do it now. - ////////////////////////////////////////////////////////////////////////////////////////// - a *= 10.; - b *= 10.; - c *= 10.; - return((b.x-a.x)*(c.y-a.y) - (b.y-a.y)*(c.x-a.x)); + return((b.x-a.x)*(p.y-a.y) - (b.y-a.y)*(p.x-a.x)); } void main() { + float subPixelFactor = 16.; + vec4 pixelColor = vec4(0.0, 1.0, 0.0, 1.0); vec4 currentColor = vec4(0., 0., 0., 1.0); - vec2 samplePoint = gl_FragCoord.xy; + ivec2 samplePoint = ivec2(gl_FragCoord.xy * subPixelFactor + vec2(0.5, 0.5)); int currentZIndex = -1; int flipCount = 0; @@ -60,22 +53,22 @@ void main() uint i1 = indexBuffer.elements[i+1]; uint i2 = indexBuffer.elements[i+2]; - vec2 p0 = vertexBuffer.elements[i0].pos; - vec2 p1 = vertexBuffer.elements[i1].pos; - vec2 p2 = vertexBuffer.elements[i2].pos; + ivec2 p0 = ivec2(vertexBuffer.elements[i0].pos * subPixelFactor + vec2(0.5, 0.5)); + ivec2 p1 = ivec2(vertexBuffer.elements[i1].pos * subPixelFactor + vec2(0.5, 0.5)); + ivec2 p2 = ivec2(vertexBuffer.elements[i2].pos * subPixelFactor + vec2(0.5, 0.5)); int zIndex = vertexBuffer.elements[i0].zIndex; vec4 color = vertexBuffer.elements[i0].color; //NOTE(martin): reorder triangle counter-clockwise and compute bias for each edge - float cw = (p1 - p0).x*(p2 - p0).y - (p1 - p0).y*(p2 - p0).x; - if(cw < 0.) + int cw = (p1 - p0).x*(p2 - p0).y - (p1 - p0).y*(p2 - p0).x; + if(cw < 0) { uint tmpIndex = i1; i1 = i2; i2 = tmpIndex; - vec2 tmpPoint = p1; + ivec2 tmpPoint = p1; p1 = p2; p2 = tmpPoint; } @@ -88,14 +81,13 @@ void main() int bias1 = is_top_left(p2, p0) ? 0 : -1; int bias2 = is_top_left(p0, p1) ? 0 : -1; - float w0 = orient2d(p1, p2, samplePoint); - float w1 = orient2d(p2, p0, samplePoint); - float w2 = orient2d(p0, p1, samplePoint); + int w0 = orient2d(p1, p2, samplePoint); + int w1 = orient2d(p2, p0, samplePoint); + int w2 = orient2d(p0, p1, samplePoint); - if((int(w0)+bias0) >= 0 && (int(w1)+bias1) >= 0 && (int(w2)+bias2) >= 0) + if((w0+bias0) >= 0 && (w1+bias1) >= 0 && (w2+bias2) >= 0) { - //TODO check cubic - vec4 cubic = (cubic0*w0 + cubic1*w1 + cubic2*w2)/(w0+w1+w2); + vec4 cubic = (cubic0*float(w0) + cubic1*float(w1) + cubic2*float(w2))/(float(w0)+float(w1)+float(w2)); float eps = 0.0001; if(cubic.w*(cubic.x*cubic.x*cubic.x - cubic.y*cubic.z) <= eps) diff --git a/src/graphics.c b/src/graphics.c index ee22343..dd272eb 100644 --- a/src/graphics.c +++ b/src/graphics.c @@ -1154,7 +1154,7 @@ void mg_offset_hull(int count, vec2* p, vec2* result, f32 offset) { ////////////////////////////////////////////////////////////////////////////////////// //WARN: quick fix for coincident middle control points - if(count == 4 && (p[1].x - p[2].x < 0.01) && (p[1].y - p[2].y < 0.01)) + if(count == 4 && (fabs(p[1].x - p[2].x) < 0.01) && (fabs(p[1].y - p[2].y) < 0.01)) { vec2 hull3[3] = {p[0], p[1], p[3]}; vec2 result3[3]; @@ -1284,6 +1284,7 @@ void mg_render_stroke_quadratic(mg_canvas_data* canvas, vec2 p[4], u32 zIndex, m // // we compute the maximum overshoot outside these bounds and split the curve at the corresponding parameter + //TODO: maybe refactor by using tolerance in the _check_, not in the computation of the overshoot f32 tolerance = minimum(attributes->tolerance, 0.5 * attributes->width); f32 d2LowBound = Square(0.5 * attributes->width - attributes->tolerance); f32 d2HighBound = Square(0.5 * attributes->width + attributes->tolerance); @@ -1316,7 +1317,6 @@ void mg_render_stroke_quadratic(mg_canvas_data* canvas, vec2 p[4], u32 zIndex, m if(maxOvershoot > 0) { - //TODO(martin): split at maxErrorParameter and recurse vec2 splitLeft[3]; vec2 splitRight[3]; mg_quadratic_split(p, maxOvershootParameter, splitLeft, splitRight); @@ -1442,6 +1442,7 @@ void mg_render_stroke_cubic(mg_canvas_data* canvas, vec2 p[4], u32 zIndex, mg_at // // we compute the maximum overshoot outside these bounds and split the curve at the corresponding parameter + //TODO: maybe refactor by using tolerance in the _check_, not in the computation of the overshoot f32 tolerance = minimum(attributes->tolerance, 0.5 * attributes->width); f32 d2LowBound = Square(0.5 * attributes->width - attributes->tolerance); f32 d2HighBound = Square(0.5 * attributes->width + attributes->tolerance); @@ -1474,17 +1475,17 @@ void mg_render_stroke_cubic(mg_canvas_data* canvas, vec2 p[4], u32 zIndex, mg_at if(maxOvershoot > 0) { - //TODO(martin): split at maxErrorParameter and recurse vec2 splitLeft[4]; vec2 splitRight[4]; mg_cubic_split(p, maxOvershootParameter, splitLeft, splitRight); mg_render_stroke_cubic(canvas, splitLeft, zIndex, attributes); mg_render_stroke_cubic(canvas, splitRight, zIndex, attributes); + + //TODO: render joint between the split curves } else { //NOTE(martin): push the actual fill commands for the offset contour - u32 zIndex = mg_get_next_z_index(canvas); mg_render_fill_cubic(canvas, positiveOffsetHull, zIndex, attributes->color); diff --git a/todo.txt b/todo.txt index 6495e68..d994ba4 100644 --- a/todo.txt +++ b/todo.txt @@ -1,198 +1,210 @@ - -[.] Check changes in macos version - [x] Restructure macos version to use mp_app_internal.h/mp_app.c - [x] test new run loop structure on macos - [x] Fix resize crash when there's no surface - [>] separate data for key and mouse event? - [>] Simplify event structs - [ ] use isARepeat in macos keyDown event and simplify update key state - - [x] use surfaces to define restricted drawing locations - [x] Implement with NSView subviews on osx - [/] Maybe switch to just using CALayers? - - [ ] Cleanup graphics backend compile-time/runtime selection - [ ] Cleanup graphics resource handles - -[>>] OpenGL surface on OSX -[>>] Port vector graphics to OpenGL on OSX -[>] Check OpenGL vector graphics on win32 -[ ] Implement surfaces with child windows on win32 - [/] Maybe implement compositing directly in d3d and opengl compat extension... - -Windows port ------------- -[.] Finish events handling - [x] window - [x] mouse move/buttons/enter/leave - [x] mouse wheel - [.] keys - [!] set key label - [x] text input - [/] pathdrop - -[x] Unify app struct and window structs for different platforms? - > define common app and window struct in mp_app_internal.h - > this file conditionally includes platform specific headers, win32_app.h, osx_app.h, etc... - > these define a macro to fill the common app and window structures with platform specific stuff. - > Common app/window proc are defined in mp_app.c - > Platform specific stuff is defined in platform specific files win32_app.c, osx_app.m, etc... - (mp_app.c can 'see' platform specific stuff, so ObjectiveC defs pose a problem, but we can define id as void* - when not in ObjC...) - -[.] Implement input polling - [ ] Simplify input polling API names - [/] Try to simplify input state and polling once we have UI usage code - -[ ] Finish win32 window create flags and properties query/setting - -[ ] Implement clipboard -[ ] Implement file dialogs -[ ] Impement resource path... -> maybe in abstracted file handling - -[ ] Clean backend selection (compile time and runtime) -[ ] Finish OpenGL loader -[ ] Test compute shaders -[ ] Initial version of vector graphics backend -[ ] Check integration of UI. - -[ ] Remove unused APIs - - - -Misc ----- -[ ] Clean-up file structure - [ ] Move stb libs to ext/ - -[ ] Renaming/clean-up pass - [ ] Separate Internal/API functions in mp_app.c - [ ] Remove MP_EVENT_KEYBOARD_MODS - [ ] Rename MP_EVENT_HIDE/SHOW to MINMIZE/UNMINIMIZE - [ ] Remove frame - [ ] Remove sequence from char event? - [ ] Replace frame_event with mp_rect - [ ] Document/unify quit/request_quit etc - [ ] Document/unify close/request_close etc - [ ] Cleanup window management - [ ] Remove unused run loop constructs - [ ] - -Shortlist ---------- -[x] let pass flat args in ui_size_push() and ui_box_set_size() -[x] separate style stacks -[x] animation time stack -[>] margins? as part of size, or different styling stack? - -[ ] Let build code set target style directly, and animate from current to target -[ ] filter styles stack by tag -[ ] image backgrounds/gradients? -[ ] animating open/close widgets? - -[.] Text box widget - [ ] Draw selection - [ ] Set cursor on click - [ ] Scroll to cursor - -Canvas Drawing --------------- -[.] Correctly handle resizing / viewport - [x] associate surfaces with mp_windows - [x] window resize resizes surface. Surface always renders to whole mp_window - [x] Add ability to create sub-views, and create surfaces for these - - window comes with a main view, so we can get a surface for the whole window - [>] Clean native mp_window_data struct (don't need to cache a lot of stuff here) - -[.] Add images bliting - [x] Clean, rename uv vs texUV stuff - [>] Destroy stuff - [>] More unified handle system - [.] Rounded images (sortof) - [ ] path clipped images - -[ ] Add color gradients? -[ ] Make canvas implicit? -[/] Handle based error signaling -[/] Allow polling events in main thread, and updating/rendering in background thread. - -[x] font metrics shouldn't depend on surface, & font - shouldn't really depend on canvas either??? - > meaning we should be able to pass fonts without canvas in ui - > and only pass canvas when drawing at the end... - -UI --- -[x] Make gui context implicit? -[x] Uniform ui_box struct + cache widgets -[x] Prune unused boxes -[.] Layout boxes - [x] basic two pass layout - [x] Layout from start or end - [>] Add overflow flags to layout, & solve conflicts - -[x] Temporarily push transform and text flip when rendering UI, so that the coord system is y down, origin at top left -[x] Canvas render the same size on a high-dpi surface - > it works with abstract 'pixel' units, which are transformed to pixels in the shader, according to backing store scaling - -[.] Style struct and style stack - [ ] Maybe use individual stack for different style attributes -[x] Pass initial style in ui_begin_frame() - -[.] Draw boxes - [x] use flags to enable/disable drawing each feature - [ ] active/hovered transitions - -[x] Change input state handling a move it to app layer -[x] Compute signals for ui_box -[x] Use ui_size_push() and pass axis instead of ui_width/height_push() -[>] Use value is ui_size as margin when kind == text or == children? - -[ ] Allow animating sizes according to hot/active? - -[ ] Basic helpers - [.] button - [.] slider (or rather, scroll bar) - [.] simple spacers - [ ] have a flag for non-cached stuff - [.] scrolling panel - [ ] Allow/disallow scrolling in x/y - [ ] Scroll with mousewheel - [/] add margins to scrollbars (disallow scrollbars crossing) - -[?] Maybe let builder code handle "active"/"hot" state, since it - depends on the widgets - [ ] On the other hand, this state must be set before layouting, in - particular font/fontSize -> maybe do a pass for static layout, - instead of doing it in box creation... - [ ] this way we can compute styling after user has set active/hot, but before layout - > Maybe just let user set style selector, and provide persistent state bits that can - > be used in any way? (to replace eg active/hot?) or perhaps not needed if we have just - > 'dragging' state - - -[x] Mask mouse outside of parent rects -> maintain clip stack and clip mouse against it -[x] Mask mouse below panels/other widgets -[x] popups and tooltips - [x] allow pushing/popping boxes irrespective of parent/child relation - [x] ui_begin_frame() prepares two containers, user ui goes in the first one - [x] tooltips and menus go to the second one - [x] Add menus -[ ] line editing widget - -Misc ----- -[x] Split metal surface and metal painter (but put them in the same compilation unit?) -[x] Have only one rect struct -[x] Shorten mp_string to str8 - -[ ] Better/Simpler time API - -[/] Frame throttling - [x] For now, we always wait on vblank during mg_surface_present(), regardless of target fps - [ ] Then actually get the correct display interval from the surface's current monitor - [ ] Allow waiting for more display interval than one? (ie allow throttling at 30fps for a 60fps display) - -[/] split osx_app and move all platform independant stuff outside - -[ ] Sort out mg_matrix_push/pop() -> transform vs. set... + +[.] Check changes in macos version + [x] Restructure macos version to use mp_app_internal.h/mp_app.c + [x] test new run loop structure on macos + [x] Fix resize crash when there's no surface + [>] separate data for key and mouse event? + [>] Simplify event structs + [ ] use isARepeat in macos keyDown event and simplify update key state + + [x] use surfaces to define restricted drawing locations + [x] Implement with NSView subviews on osx + [/] Maybe switch to just using CALayers? + + [ ] Cleanup graphics backend compile-time/runtime selection + [ ] Cleanup graphics resource handles + +[.] GLES 3.0 surface on OSX +[x] GLES 3.1 surface on Win32 +[.] GLES vector graphics on win32 + [x] Fix triangle rasterization precision issues + -> we do not want to snap vertex coordinates to integers though, but use fixed point with 4 or 8 bits of subpixel precision + -> convert verts pos to fixed point + -> do orient2d in fixed point + [!] Check precision/possible overflow when using barycentric coords + + [>>] Investigate cubics flipping when curves are disabled + [>>] Investigate bad curve splitting on the right? + [ ] Multi-sampling + + [>] Avoid first useless (degenerate) triangle on every path + +[ ] Implement surfaces with child windows on win32 + [/] Maybe implement compositing directly in d3d and opengl compat extension... + +Windows port +------------ +[.] Finish events handling + [x] window + [x] mouse move/buttons/enter/leave + [x] mouse wheel + [.] keys + [!] set key label + [x] text input + [/] pathdrop + +[x] Unify app struct and window structs for different platforms? + > define common app and window struct in mp_app_internal.h + > this file conditionally includes platform specific headers, win32_app.h, osx_app.h, etc... + > these define a macro to fill the common app and window structures with platform specific stuff. + > Common app/window proc are defined in mp_app.c + > Platform specific stuff is defined in platform specific files win32_app.c, osx_app.m, etc... + (mp_app.c can 'see' platform specific stuff, so ObjectiveC defs pose a problem, but we can define id as void* + when not in ObjC...) + +[.] Implement input polling + [ ] Simplify input polling API names + [/] Try to simplify input state and polling once we have UI usage code + +[ ] Finish win32 window create flags and properties query/setting + +[ ] Implement clipboard +[ ] Implement file dialogs +[ ] Impement resource path... -> maybe in abstracted file handling + +[ ] Clean backend selection (compile time and runtime) +[ ] Finish OpenGL loader +[ ] Test compute shaders +[ ] Initial version of vector graphics backend +[ ] Check integration of UI. + +[ ] Remove unused APIs + + + +Misc +---- +[ ] Clean-up file structure + [ ] Move stb libs to ext/ + +[ ] Renaming/clean-up pass + [ ] Separate Internal/API functions in mp_app.c + [ ] Remove MP_EVENT_KEYBOARD_MODS + [ ] Rename MP_EVENT_HIDE/SHOW to MINMIZE/UNMINIMIZE + [ ] Remove frame + [ ] Remove sequence from char event? + [ ] Replace frame_event with mp_rect + [ ] Document/unify quit/request_quit etc + [ ] Document/unify close/request_close etc + [ ] Cleanup window management + [ ] Remove unused run loop constructs + [ ] + +Shortlist +--------- +[x] let pass flat args in ui_size_push() and ui_box_set_size() +[x] separate style stacks +[x] animation time stack +[>] margins? as part of size, or different styling stack? + +[ ] Let build code set target style directly, and animate from current to target +[ ] filter styles stack by tag +[ ] image backgrounds/gradients? +[ ] animating open/close widgets? + +[.] Text box widget + [ ] Draw selection + [ ] Set cursor on click + [ ] Scroll to cursor + +Canvas Drawing +-------------- +[.] Correctly handle resizing / viewport + [x] associate surfaces with mp_windows + [x] window resize resizes surface. Surface always renders to whole mp_window + [x] Add ability to create sub-views, and create surfaces for these + - window comes with a main view, so we can get a surface for the whole window + [>] Clean native mp_window_data struct (don't need to cache a lot of stuff here) + +[.] Add images bliting + [x] Clean, rename uv vs texUV stuff + [>] Destroy stuff + [>] More unified handle system + [.] Rounded images (sortof) + [ ] path clipped images + +[ ] Add color gradients? +[ ] Make canvas implicit? +[/] Handle based error signaling +[/] Allow polling events in main thread, and updating/rendering in background thread. + +[x] font metrics shouldn't depend on surface, & font + shouldn't really depend on canvas either??? + > meaning we should be able to pass fonts without canvas in ui + > and only pass canvas when drawing at the end... + +UI +-- +[x] Make gui context implicit? +[x] Uniform ui_box struct + cache widgets +[x] Prune unused boxes +[.] Layout boxes + [x] basic two pass layout + [x] Layout from start or end + [>] Add overflow flags to layout, & solve conflicts + +[x] Temporarily push transform and text flip when rendering UI, so that the coord system is y down, origin at top left +[x] Canvas render the same size on a high-dpi surface + > it works with abstract 'pixel' units, which are transformed to pixels in the shader, according to backing store scaling + +[.] Style struct and style stack + [ ] Maybe use individual stack for different style attributes +[x] Pass initial style in ui_begin_frame() + +[.] Draw boxes + [x] use flags to enable/disable drawing each feature + [ ] active/hovered transitions + +[x] Change input state handling a move it to app layer +[x] Compute signals for ui_box +[x] Use ui_size_push() and pass axis instead of ui_width/height_push() +[>] Use value is ui_size as margin when kind == text or == children? + +[ ] Allow animating sizes according to hot/active? + +[ ] Basic helpers + [.] button + [.] slider (or rather, scroll bar) + [.] simple spacers + [ ] have a flag for non-cached stuff + [.] scrolling panel + [ ] Allow/disallow scrolling in x/y + [ ] Scroll with mousewheel + [/] add margins to scrollbars (disallow scrollbars crossing) + +[?] Maybe let builder code handle "active"/"hot" state, since it + depends on the widgets + [ ] On the other hand, this state must be set before layouting, in + particular font/fontSize -> maybe do a pass for static layout, + instead of doing it in box creation... + [ ] this way we can compute styling after user has set active/hot, but before layout + > Maybe just let user set style selector, and provide persistent state bits that can + > be used in any way? (to replace eg active/hot?) or perhaps not needed if we have just + > 'dragging' state + + +[x] Mask mouse outside of parent rects -> maintain clip stack and clip mouse against it +[x] Mask mouse below panels/other widgets +[x] popups and tooltips + [x] allow pushing/popping boxes irrespective of parent/child relation + [x] ui_begin_frame() prepares two containers, user ui goes in the first one + [x] tooltips and menus go to the second one + [x] Add menus +[ ] line editing widget + +Misc +---- +[x] Split metal surface and metal painter (but put them in the same compilation unit?) +[x] Have only one rect struct +[x] Shorten mp_string to str8 + +[ ] Better/Simpler time API + +[/] Frame throttling + [x] For now, we always wait on vblank during mg_surface_present(), regardless of target fps + [ ] Then actually get the correct display interval from the surface's current monitor + [ ] Allow waiting for more display interval than one? (ie allow throttling at 30fps for a 60fps display) + +[/] split osx_app and move all platform independant stuff outside + +[ ] Sort out mg_matrix_push/pop() -> transform vs. set...