From 3ed37366314eb2faa41126892e488955f3c12bec Mon Sep 17 00:00:00 2001 From: Ronja Date: Sun, 11 Oct 2020 17:22:57 +0200 Subject: [PATCH] scroll list and minor fixes --- Luxe/assets/wip/Knob3.image.lx | 3 + Luxe/assets/wip/Knob3.png | 3 + Luxe/blocks/ui/adventure.wren | 88 ++++++++- Luxe/blocks/ui/compass.wren | 7 +- Luxe/blocks/ui/scroll_box.wren | 151 +++++++++++++++ Luxe/blocks/ui/slider.wren | 81 ++++++-- Luxe/game.wren | 2 + Luxe/math/math.wren | 4 + Luxe/shaders/pixel_text_ui.emsl | 188 +++++++++++-------- Luxe/shaders/pixel_text_ui.material_basis.lx | 15 +- 10 files changed, 445 insertions(+), 97 deletions(-) create mode 100644 Luxe/assets/wip/Knob3.image.lx create mode 100644 Luxe/assets/wip/Knob3.png create mode 100644 Luxe/blocks/ui/scroll_box.wren diff --git a/Luxe/assets/wip/Knob3.image.lx b/Luxe/assets/wip/Knob3.image.lx new file mode 100644 index 0000000..f6832dd --- /dev/null +++ b/Luxe/assets/wip/Knob3.image.lx @@ -0,0 +1,3 @@ +image = { + source = "assets/wip/Knob3.png" +} \ No newline at end of file diff --git a/Luxe/assets/wip/Knob3.png b/Luxe/assets/wip/Knob3.png new file mode 100644 index 0000000..bd62f8d --- /dev/null +++ b/Luxe/assets/wip/Knob3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:95367ce6e1b9ad58596bb2e5c49f8fab99bba7b12056eafc582cb4257c3f040c +size 97 diff --git a/Luxe/blocks/ui/adventure.wren b/Luxe/blocks/ui/adventure.wren index 64d1015..4a4afc2 100644 --- a/Luxe/blocks/ui/adventure.wren +++ b/Luxe/blocks/ui/adventure.wren @@ -16,8 +16,9 @@ import "blocks/ui/simple_text" for UISimpleText import "blocks/ui/ui" for Ui import "blocks/ui/slider" for UiSlider import "blocks/ui/box" for UiBox +import "blocks/ui/scroll_box" for UiScrollBox import "blocks/ui/compass" for UiCompass -import "blocks/ui/info" for UiInfo //this is a cyclic dependency waiting to happen +import "blocks/ui/info" for UiInfo //this is a cyclic dependency waiting to happen... import "math/math" for M import "math/util" for Util import "blocks/human/human" for Human @@ -197,10 +198,8 @@ class UiAdventure{ } } - var list = UiBox.create(_ent) + var list = UiScrollBox.create(_ent) Control.child_add(page, list) - Control.set_clip(list, true) - UILayout.set_contain(_ent, list, UILayoutContain.column | UILayoutContain.start) //| UILayout.set_behave(_ent, list, UILayoutBehave.fill) UILayout.set_margin(_ent, list, 40, 0, 0, 0) @@ -218,14 +217,16 @@ class UiAdventure{ if(!adventure) return //rebuild + var i = -1 adventure.adventurers.each{ |adventurer| + i = i + 1 var item = UiBox.create(_ent) - Control.child_add(list, item) UILayout.set_behave(_ent, item, UILayoutBehave.left | UILayoutBehave.right)//| UILayout.set_margin(_ent, item, 0, 0, 0, 0) UILayout.set_contain(_ent, item, UILayoutContain.row | UILayoutContain.start)//| Control.set_size(item, -1, 12) Control.set_allow_input(item, true) + Control.set_id(item, "Person %(i)") Control.set_events(item) {|event| if(UI.event_cancelled(_ent, event.id)) return if(event.type == UIEvent.press){ @@ -234,6 +235,7 @@ class UiAdventure{ _game.focus.value = adventurer } } + UiScrollBox.add(list, item) var head = UIImage.create(_ent) Control.child_add(item, head) @@ -284,6 +286,82 @@ class UiAdventure{ _page.on_change(true){|val| Control.set_visible(page, val == UiAdventure.resources) } + + var list = UiScrollBox.create(_ent) + Control.child_add(page, list) + UILayout.set_behave(_ent, list, UILayoutBehave.fill) + UILayout.set_margin(_ent, list, 40, 0, 0, 0) + + + var tiny_head = Assets.image("assets/wip/8Head") + var x_image = Assets.image("assets/wip/8Cross") + + _itemListItems = [] + _game.adventures.planning.on_change(true) {|adventure| + //cleanup + _itemListItems.each{|item| + Control.destroy(item) + } + _itemListItems.clear() + + if(!adventure) return + + //rebuild + adventure.adventurers.each{ |adventurer| + var item = UiBox.create(_ent) + UILayout.set_behave(_ent, item, UILayoutBehave.left | UILayoutBehave.right)//| + UILayout.set_margin(_ent, item, 0, 0, 0, 0) + UILayout.set_contain(_ent, item, UILayoutContain.row | UILayoutContain.start)//| + Control.set_size(item, -1, 12) + Control.set_allow_input(item, true) + Control.set_events(item) {|event| + if(UI.event_cancelled(_ent, event.id)) return + if(event.type == UIEvent.press){ + _ui.ui_mode = Ui.Info + _ui.info.page.value = UiInfo.human + _game.focus.value = adventurer + } + } + UiScrollBox.add(list, item) + + var head = UIImage.create(_ent) + Control.child_add(item, head) + Control.set_size(head, 8, 8) + UIImage.set_image(head, tiny_head) + UIImage.set_color(head, Human.get_color(adventurer)) + UILayout.set_margin(_ent, head, 2, 0, 0, 0) + + var name = UILabel.create(_ent) + Control.child_add(item, name) + UILabel.set_align_vertical(name, TextAlign.bottom) + UILabel.set_font(name, _ui.font) + UILabel.set_text_size(name, 8) + UILabel.set_text(name, Human.get_name(adventurer)) + UILayout.set_behave(_ent, name, UILayoutBehave.hfill | UILayoutBehave.left)//| + UILayout.set_margin(_ent, name, 2, 1, 0, 1) + + var remove = ImageButton.create(_ent) + Control.child_add(item, remove) + Control.set_size(remove, 8, 8) + UIImage.set_image(remove, x_image) + UIImage.set_color(remove, Color.hex(0xec172a)) + UILayout.set_margin(_ent, remove, 0, 0, 2, 0) + ImageButton.set_tooltip(remove, "remove") + ImageButton.set_state_change(remove) { |data, button| + if(data["press"]){ + Frame.end{ + Globals["Tooltip"].clear(button) + Util.remove(adventure.adventurers, adventurer) + _game.adventures.planning.emit() + } + } + } + + _itemListItems.add(item) + } + UILayout.commit(_ent) + UI.commit(_ent) + } } depart(){ diff --git a/Luxe/blocks/ui/compass.wren b/Luxe/blocks/ui/compass.wren index d626fc3..38a0074 100644 --- a/Luxe/blocks/ui/compass.wren +++ b/Luxe/blocks/ui/compass.wren @@ -37,7 +37,7 @@ class UiCompass{ if(event.control != control) return if(event.type == UIEvent.press){ - var relative_pos = AABB.grow(AABB.new(x, y, w, h),[2, 2]).relative_pos([event.x, event.y]) + var relative_pos = AABB.new(x, y, w, h).relative_pos([event.x, event.y]) var inside = M.length(relative_pos.map{|comp| comp - 0.5}) < 0.5 if(inside) { state["pressed"] = true @@ -46,10 +46,11 @@ class UiCompass{ } else if(event.type == UIEvent.release){ state["pressed"] = false UI.uncapture(control) - } else if(event.type == UIEvent.move && state["pressed"]){ + } + if((event.type == UIEvent.move || event.type == event.type == UIEvent.press) && state["pressed"]){ var center = [x + w/2, y + h/2] var diff = [event.x - center.x, event.y - center.y] - var angle = Math.atan2(-diff.y, diff.x) - Num.tau / 4 + var angle = Math.atan2(-diff.y, diff.x) - Num.tau / 4 //flip y because ui coordinates are different; shift by 1/4 circle constant to make up 0° UI.events_emit(control, UIEvent.change, Math.degrees(angle)) } } diff --git a/Luxe/blocks/ui/scroll_box.wren b/Luxe/blocks/ui/scroll_box.wren new file mode 100644 index 0000000..9fd11bb --- /dev/null +++ b/Luxe/blocks/ui/scroll_box.wren @@ -0,0 +1,151 @@ +import "luxe: ui/control" for Control +import "luxe: world" for UI, World, UIEvent, UILayout, UILayoutContain, UILayoutBehave +import "luxe: draw" for PathStyle +import "luxe: assets" for Assets +import "luxe: render" for Image +import "luxe: math" for Math +import "luxe: game" for Frame + +import "globals" for Globals +import "math/rect" for AABB +import "math/math" for M +import "blocks/debug" for DrawDebug +import "blocks/ui/slider" for UiSlider + +class UiScrollBox{ + + static create(ent){ + var box = Control.create(ent) + var style = PathStyle.new() + style.color = [1,1,1,1] + style.thickness = 1 + Control.set_allow_input(box, true) + var state = {"style": style, "pressed": false, "position": 0, "scrollSpeed": 4, "ent": ent, "spring": 32, "contentHeight": 0} + Control.set_state_data(box, state) + + var slider = UiSlider.create(ent) + Control.child_add(box, slider) + UiSlider.set_line(slider, false) + UiSlider.set_direction(slider, UiSlider.vertical) + UiSlider.set_handle(slider, Assets.image("assets/wip/Knob3")) + UiSlider.set_value(slider, 0) + Control.set_size(slider, 7, -1) + UILayout.set_behave(ent, slider, UILayoutBehave.right | UILayoutBehave.vfill) //| + UILayout.set_margin(ent, slider, 1, 0, 1, 0) + Control.set_events(slider){|event| + if(event.type == UIEvent.change){ + var position = 0 + var max_height = (Control.get_height(state["childContainer"]) + 1) - state["contentHeight"] + if(max_height < 0){ + position = M.lerp(0, max_height, event.change) + } + set_position(box, position) + } + } + state["slider"] = slider + + var childContainer = Control.create(ent) + Control.child_add(box, childContainer) + UILayout.set_behave(ent, childContainer, UILayoutBehave.fill) + UILayout.set_margin(ent, childContainer, 1, 1, 7, 2) //don't include border, fix bottom bug, freedom to side slider + Control.set_clip(childContainer, true) + UILayout.set_contain(ent, childContainer, UILayoutContain.column | UILayoutContain.start) //| + state["childContainer"] = childContainer + + var alignmentChild = Control.create(ent) + Control.child_add(childContainer, alignmentChild) + Control.set_size(alignmentChild, -1, 0) + UILayout.set_behave(ent, alignmentChild, UILayoutBehave.hfill | UILayoutBehave.top) //| //make it wide + UILayout.set_margin(ent, alignmentChild, 0, state["position"], 0, 0) //pos will always be 0 here, but writing it explicitly makes intent clearer + state["alignmentChild"] = alignmentChild + + Control.set_render(box) {|control, state, x, y, w, h| + var scrollPos = state["position"] + if(scrollPos >= 0.5){ + scrollPos = M.lerp(scrollPos, 0, M.pow2(-state["spring"] * Globals["Delta"])) + set_position(control, scrollPos, true) + } + var container_height = Control.get_height(childContainer) + 1 //+1 is here to fix clip bug + var max_pos = Math.min(container_height - state["contentHeight"], 0) + if(scrollPos <= max_pos - 0.5){ + scrollPos = M.lerp(scrollPos, max_pos, M.pow2(-state["spring"] * Globals["Delta"])) + set_position(control, scrollPos, true) + } + + var depth = UI.draw_depth_of(control, 0) + UI.draw_rect(control, x+0.5, y+0.5, depth, w-1, h-1, 0, state["style"]) + } + + Control.set_process(box){|control, state, event, x, y, w, h| + if(event.control != control) return + + if(event.type == UIEvent.scroll){ + var scrollPos = state["position"] + scrollPos = scrollPos + event.y * state["scrollSpeed"] * -1 + set_position(control, scrollPos) + } + } + + return box + } + + static add(box, child){ + var state = Control.get_state_data(box) + Control.child_add(state["childContainer"], child) + var ent = state["ent"] + UILayout.commit(ent) + UI.commit(ent) + update_content(box) + } + + static remove(box, child){ + var state = Control.get_state_data(box) + Control.child_remove(state["childContainer"], child) + var ent = state["ent"] + UILayout.commit(ent) + UI.commit(ent) + update_content(box) + } + + static update_content(box){ + var state = Control.get_state_data(box) + var container = state["childContainer"] + var child_count = Control.child_count(container) + + //if only the alignmentChild exists + if(child_count <= 1){ + state["contentHeight"] = 0 + return + } + + var first_child = Control.child_get(container, 1) + var upper_bound = Control.get_pos_y_abs(first_child) + var last_child = Control.child_get(container, child_count - 1) + var lower_bound = Control.get_pos_y_abs(last_child) + Control.get_height(last_child) + state["contentHeight"] = lower_bound - upper_bound //its lower - upper because of the y-negative coordinates + } + + static set_position(box, position) { + set_position(box, position, false) + } + + static set_position(box, position, delay_commit){ + var state = Control.get_state_data(box) + state["position"] = position + var ent = state["ent"] + UILayout.set_margin(ent, state["alignmentChild"], 0, position, 0, 0) + if(!delay_commit){ + UILayout.commit(ent) + } else { + Frame.end{ + UILayout.commit(ent) + } + } + var rel_pos = 0 + var max_height = (Control.get_height(state["childContainer"]) + 1) - state["contentHeight"] + if(max_height < 0){ + rel_pos = M.clamp(M.inv_lerp(0, max_height, position), 0, 1) + } + UiSlider.set_value(state["slider"], rel_pos) + } +} \ No newline at end of file diff --git a/Luxe/blocks/ui/slider.wren b/Luxe/blocks/ui/slider.wren index 44f7762..e27a6ce 100644 --- a/Luxe/blocks/ui/slider.wren +++ b/Luxe/blocks/ui/slider.wren @@ -11,25 +11,44 @@ import "math/math" for M import "blocks/debug" for DrawDebug class UiSlider{ + static horizontal{"horiz"} + static vertical{"vert"} static create(ent){ - var compass = Control.create(ent) + var slider = Control.create(ent) var style = PathStyle.new() style.color = [1,1,1,1] style.thickness = 1 - Control.set_allow_input(compass, true) - Control.set_state_data(compass, {"value": 0.5, "style": style, "pressed": false}) //angle is in radians between 0 and tau - Control.set_render(compass) {|control, state, x, y, w, h| + Control.set_allow_input(slider, true) + var state = {"value": 0.2, + "style": style, + "pressed": false, + "centerLine": true, + "handle": Assets.image("assets/wip/SliderHandle"), + "direction": UiSlider.horizontal + } + Control.set_state_data(slider, state) + Control.set_render(slider) {|control, state, x, y, w, h| var depth = UI.draw_depth_of(control, 0) UI.draw_rect(control, x+0.5, y+0.5, depth, w-1, h-1, 0, state["style"]) - UI.draw_line(control, x + h/2, y + h/2, x + w - h/2, y + h/2, depth, state["style"]) - var image = Assets.image("assets/wip/SliderHandle") + if(state["centerLine"]) { + UI.draw_line(control, x + h/2, y + h/2, x + w - h/2, y + h/2, depth, state["style"]) + } + var image = state["handle"] var img_size = [Image.get_width(image), Image.get_height(image)] - UI.draw_image(control, (M.lerp(x+h/2, x+w-h/2, state["value"]) - img_size.x/2).round, - y+h/2-img_size.y/2, depth, img_size.x, img_size.y, + var slider_x + var slider_y + if(state["direction"] == UiSlider.horizontal){ + slider_x = (M.lerp(x+h/2, x+w-h/2, state["value"]) - img_size.x/2).round + slider_y = y+h/2-img_size.y/2 + } else if(state["direction"] == UiSlider.vertical){ + slider_x = x+w/2-img_size.x/2 + slider_y = (M.lerp(y+w/2, y+h-w/2, state["value"]) - img_size.y/2).round + } + UI.draw_image(control, slider_x, slider_y, depth, img_size.x, img_size.y, 0, [1,1,1,1], [0, 0, 1, 1], image, true) } - Control.set_process(compass){|control, state, event, x,y,w,h| + Control.set_process(slider){|control, state, event, x,y,w,h| if(event.control != control) return if(event.type == UIEvent.press){ state["pressed"] = true @@ -37,14 +56,36 @@ class UiSlider{ } else if(event.type == UIEvent.release){ state["pressed"] = false UI.uncapture(control) - } else if(event.type == UIEvent.move && state["pressed"]){ - var abs_x = Control.get_pos_x_abs(control) - var rel_pos = M.inv_lerp(abs_x+h/2, abs_x+w-h/2, event.x) + } + if((event.type == UIEvent.move || event.type == UIEvent.press) && state["pressed"]){ + var min + var max + var value + if(state["direction"] == UiSlider.horizontal){ + var abs_x = Control.get_pos_x_abs(control) + min = abs_x+h/2 + max = abs_x+w-h/2 + value = event.x + } else if(state["direction"] == UiSlider.vertical){ + var abs_y = Control.get_pos_y_abs(control) + min = abs_y+w/2 + max = abs_y+h-w/2 + value = event.y + } + var rel_pos = M.inv_lerp(min, max, value) rel_pos = M.clamp(rel_pos, 0, 1) UI.events_emit(control, UIEvent.change, rel_pos) } } - return compass + + //default events impl, overridable! + Control.set_events(slider){|event| + if(event.type == UIEvent.change){ + set_value(slider, event.change) + } + } + + return slider } static set_value(slider, value){ @@ -52,4 +93,18 @@ class UiSlider{ data["value"] = value } + static set_line(slider, line){ + var data = Control.get_state_data(slider) + data["centerLine"] = line + } + + static set_direction(slider, direction){ + var data = Control.get_state_data(slider) + data["direction"] = direction + } + + static set_handle(slider, handle){ + var data = Control.get_state_data(slider) + data["handle"] = handle + } } \ No newline at end of file diff --git a/Luxe/game.wren b/Luxe/game.wren index fdb3549..e516426 100644 --- a/Luxe/game.wren +++ b/Luxe/game.wren @@ -42,6 +42,8 @@ class Game is Ready { } //ready tick(delta) { + Globals["Delta"] = delta + var mouse_pos = Vector.new(Input.mouse_x(), Input.mouse_y()) var game_mouse = Globals["Renderer"].game_mouse(mouse_pos) game_mouse = Camera.screen_point_to_world(app.camera, game_mouse.x, game_mouse.y) diff --git a/Luxe/math/math.wren b/Luxe/math/math.wren index d14888f..1f6f290 100644 --- a/Luxe/math/math.wren +++ b/Luxe/math/math.wren @@ -26,6 +26,10 @@ class M{ return result } + static pow2(value){ + return value * value + } + static lerp(from, to, value){ //if the range are numbers, we assume the interpolation value is too if(from is Num && to is Num) return from + (to - from) * value diff --git a/Luxe/shaders/pixel_text_ui.emsl b/Luxe/shaders/pixel_text_ui.emsl index 0215627..a4f33fb 100644 --- a/Luxe/shaders/pixel_text_ui.emsl +++ b/Luxe/shaders/pixel_text_ui.emsl @@ -1,51 +1,44 @@ input View { - mat4 mvp, - mat4 proj, - mat4 proj_inverse, - mat4 view, - mat4 world, //geometry.world atm - float2 fov, //fov.x, fov.y - float2 resolution, - float4 target_region, - float4 target_region_size + mat4 mvp, + mat4 proj, + mat4 proj_inverse, + mat4 view, + mat4 world, //geometry.world atm + float2 fov, //fov.x, fov.y + float2 resolution, + float4 target_region, + float4 target_region_size } input Font { - image2D pages[8] + image2D pages[8] +} + +input UI { + float2 canvas_size, + float textured, + float pixelated, + #0 image2D tex_mask, + #1 image2D tex, + #1 image2D tex_pixelated } input FUI { - float2 canvas_size, - #0 image2D tex_mask + float2 canvas_size, + #0 image2D tex_mask } -stage vertex vert( - input { View view }, + stage vertex vert( + input { View view, UI ui }, vertex in { #0 float4 pos, #1 float4 color, #2 float2 uv, - #3 float4 data + #3 float4 data, + #4 float2 bounds }, fragment out { - float4 color, - float2 uv, - float2 mask_uv, - float clip - }) -{ - out.uv = in.uv; - out.color = in.color; - out.clip = in.data.x; - out.mask_uv = float2(in.data.y, 1.0 - in.data.z); - - stage.pos = input.view.mvp * float4(in.pos.xyz, 1.0); -} - -stage fragment frag( - input { FUI ui, Font font }, - fragment in { float4 color, float2 uv, float2 mask_uv, @@ -53,52 +46,99 @@ stage fragment frag( float page }) { - //outside mask uvs? - bool outside_mask = in.mask_uv.x < 0.0 || - in.mask_uv.x > 1.0 || - in.mask_uv.y < 0.0 || - in.mask_uv.y > 1.0; - - if(outside_mask) { - discard; - // stage.color[0] = float4(1,0.2,0.3,1); - return; - } + out.uv = in.uv; + out.color = in.color; + out.clip = in.data.x; + out.page = in.pos.w; - float4 mask = texture(input.ui.tex_mask, in.mask_uv); + //for fonts the text pos is text local and needs to be converted to canvas space. + //we pass the bounds of the text item in via the vertices (I know) so we can calculate bounds-local space. + //Then we convert that to canvas space, by * (bounds_w/canvas_w) + //Then finally we add to the canvas local u/v from the data.z/w in - //16 bit mask - // float mask_clip = round(mask.r * 65535.0); - //8 bit mask - int lsb = int(round(mask.r * 255.0)); - int msb = int(round(mask.g * 255.0)) * 256; //<< 8; - int mask_clip = lsb | msb; - // int mask_clip = lsb + msb; //webgl1 + float bounds_u = 0.0; + float bounds_v = 0.0; + if(in.bounds.x != 0.0) { bounds_u = (in.pos.x / in.bounds.x); } + if(in.bounds.y != 0.0) { bounds_v = 1.0 - (in.pos.y / in.bounds.y); } - //outside our clipping bounds? - int my_clip = int(in.clip); - if(my_clip != 0 && mask_clip < my_clip) { - discard; - // stage.color[0] = float4(0,1,1,0.2); - return; - } + //bounds_u/v is the 0...1 value for where this vert is in the text item itself + //Note we flip the bounds_v because the text is in world space, bottom left origin, y+ up, + //canvas space is top left, y+ down so it has to be reversed to be bounds local in canvas space - int page = int(in.page); - float4 msdf_sample = texture(input.font.pages[0], in.uv.xy); - if(page == 1) { msdf_sample = texture(input.font.pages[1], in.uv.xy); } - if(page == 2) { msdf_sample = texture(input.font.pages[2], in.uv.xy); } - if(page == 3) { msdf_sample = texture(input.font.pages[3], in.uv.xy); } - if(page == 4) { msdf_sample = texture(input.font.pages[4], in.uv.xy); } - if(page == 5) { msdf_sample = texture(input.font.pages[5], in.uv.xy); } - if(page == 6) { msdf_sample = texture(input.font.pages[6], in.uv.xy); } - if(page == 7) { msdf_sample = texture(input.font.pages[7], in.uv.xy); } + float to_canvas_u = in.bounds.x / input.ui.canvas_size.x; + float to_canvas_v = in.bounds.y / input.ui.canvas_size.y; - float r = msdf_sample.r; - float g = msdf_sample.g; - float b = msdf_sample.b; - float median = max(min(r, g), min(max(r, g), b)); - float opacity = float(median > 0.5); + //to_canvas_u is the text item width and height in 0...1 relative to the canvas + //e.g if the text item width is 100 and the canvas is 200, the value is 0.5 - stage.color[0] = float4(in.color.rgb, in.color.a * opacity * mask.a); + //then what we do is add in.data.y/z which is the text x/y position, + //but relative to the canvas space. we add this to shift the x/y into place. -} \ No newline at end of file + bounds_u = (bounds_u * to_canvas_u) + in.data.y; + bounds_v = (bounds_v * to_canvas_v) + in.data.z; + + out.mask_uv = float2(bounds_u, bounds_v); + + stage.pos = input.view.mvp * float4(in.pos.xyz, 1.0); +} + +stage fragment frag( + input { FUI ui, Font font }, + fragment in { + float4 color, + float2 uv, + float2 mask_uv, + float clip, + float page + }) + { + + //outside mask uvs? + bool outside_mask = in.mask_uv.x < 0.0 || + in.mask_uv.x > 1.0 || + in.mask_uv.y < 0.0 || + in.mask_uv.y > 1.0; + + if(outside_mask) { + discard; + // stage.color[0] = float4(1,0.2,0.3,1); + return; + } + + float4 mask = texture(input.ui.tex_mask, in.mask_uv); + + //16 bit mask + // float mask_clip = round(mask.r * 65535.0); + //8 bit mask + int lsb = int(round(mask.r * 255.0)); + int msb = int(round(mask.g * 255.0)) * 256; //<< 8; + int mask_clip = lsb | msb; + // int mask_clip = lsb + msb; //webgl1 + + //outside our clipping bounds? + int my_clip = int(in.clip); + if(my_clip != 0 && mask_clip < my_clip) { + discard; + // stage.color[0] = float4(0,1,1,0.2); + return; + } + + int page = int(in.page); + float4 msdf_sample = texture(input.font.pages[0], in.uv.xy); + if(page == 1) { msdf_sample = texture(input.font.pages[1], in.uv.xy); } + if(page == 2) { msdf_sample = texture(input.font.pages[2], in.uv.xy); } + if(page == 3) { msdf_sample = texture(input.font.pages[3], in.uv.xy); } + if(page == 4) { msdf_sample = texture(input.font.pages[4], in.uv.xy); } + if(page == 5) { msdf_sample = texture(input.font.pages[5], in.uv.xy); } + if(page == 6) { msdf_sample = texture(input.font.pages[6], in.uv.xy); } + if(page == 7) { msdf_sample = texture(input.font.pages[7], in.uv.xy); } + + float r = msdf_sample.r; + float g = msdf_sample.g; + float b = msdf_sample.b; + float median = max(min(r, g), min(max(r, g), b)); + float opacity = float(median > 0.5); + + stage.color[0] = float4(in.color.rgb, in.color.a * opacity * mask.a); + + } \ No newline at end of file diff --git a/Luxe/shaders/pixel_text_ui.material_basis.lx b/Luxe/shaders/pixel_text_ui.material_basis.lx index fe97f85..31381f6 100644 --- a/Luxe/shaders/pixel_text_ui.material_basis.lx +++ b/Luxe/shaders/pixel_text_ui.material_basis.lx @@ -1,11 +1,11 @@ material_basis = { - vertex_format = "luxe.textured" + vertex_format = "luxe.ui_font" shaders = { vertex = { library="shaders/pixel_text_ui" function="vert" } fragment = { library="shaders/pixel_text_ui" function="frag" } } depth_test = true - depth_write = false + depth_write = true depth_compare = "less_equal" stencil_test = false write_mask = { red=true green=true blue=true alpha=true } @@ -21,6 +21,13 @@ material_basis = { winding = "counter_clockwise" layers = ["default"] inputs = { + ui.tex_mask = { + type = "image" + value = { + type = "image2D" + sampler_state = "nearest_clamp" + } + } font.pages = { type = "image" count = 8 @@ -59,5 +66,9 @@ material_basis = { } ] } + ui.canvas_size = { + type = "float2" + value = [1 1] + } } } \ No newline at end of file