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 import "math/util" for Util 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, "drag_cancelled":true} 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, 0, 0, 0, 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) //| Control.set_allow_input(childContainer, true) Control.set_events(childContainer){ |event| if(!Util.valid_event(event, state["drag_cancelled"])) return if(event.type == UIEvent.press) { Control.set_state_data(childContainer, true) UI.capture(childContainer) } if(event.type == UIEvent.release) { Control.set_state_data(childContainer, false) UI.uncapture(childContainer) } if(event.type == UIEvent.move && Control.get_state_data(childContainer)){ var state = Control.get_state_data(box) var scrollPos = state["position"] scrollPos = scrollPos + event.y_rel + 128 set_position(box, scrollPos) } } 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_drag_cancelled(box, drag_cancelled){ var state = Control.get_state_data(box) state["drag_cancelled"] = drag_cancelled } 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) } }