|
1 | 1 | SMODS.GUI = {} |
2 | 2 | SMODS.GUI.DynamicUIManager = {} |
3 | 3 |
|
| 4 | +-- used to properly truncate overflow content inside another overflow content |
| 5 | +SMODS.stencil_stack = {} |
| 6 | + |
| 7 | +function SMODS.push_to_stencil_stack(stencil_fn) |
| 8 | + assert(type(stencil_fn) == "function", "No stencil function passed to SMODS.push_to_stencil_stack") |
| 9 | + local old_level = #SMODS.stencil_stack |
| 10 | + local new_level = old_level + 1 |
| 11 | + |
| 12 | + love.graphics.setStencilTest("equal", old_level) |
| 13 | + love.graphics.stencil(function() |
| 14 | + stencil_fn(false) |
| 15 | + end, "increment", 1, true) |
| 16 | + love.graphics.setStencilTest("equal", new_level) |
| 17 | + |
| 18 | + SMODS.stencil_stack[new_level] = stencil_fn |
| 19 | +end |
| 20 | +function SMODS.pop_from_stencil_stack() |
| 21 | + local old_level = #SMODS.stencil_stack |
| 22 | + local new_level = old_level - 1 |
| 23 | + |
| 24 | + local stencil_fn = SMODS.stencil_stack[old_level] |
| 25 | + if not stencil_fn then |
| 26 | + return |
| 27 | + end |
| 28 | + |
| 29 | + love.graphics.setStencilTest("equal", old_level) |
| 30 | + love.graphics.stencil(function() |
| 31 | + stencil_fn(true) |
| 32 | + end, "decrement", 1, true) |
| 33 | + love.graphics.setStencilTest("equal", new_level) |
| 34 | + |
| 35 | + SMODS.stencil_stack[old_level] = nil |
| 36 | +end |
| 37 | +function SMODS.reset_stencil_stack() |
| 38 | + EMPTY(SMODS.stencil_stack) |
| 39 | + love.graphics.setStencilTest() |
| 40 | + love.graphics.stencil(function() end) |
| 41 | +end |
| 42 | +function SMODS.reload_stencil_stack() |
| 43 | + local stack_snapshot = SMODS.shallow_copy(SMODS.stencil_stack) |
| 44 | + SMODS.reset_stencil_stack() |
| 45 | + for _, stencil_fn in ipairs(stack_snapshot) do |
| 46 | + SMODS.push_to_stencil_stack(stencil_fn) |
| 47 | + end |
| 48 | +end |
| 49 | + |
| 50 | +local gameDrawRef = Game.draw |
| 51 | +function Game:draw(...) |
| 52 | + SMODS.reset_stencil_stack() |
| 53 | + gameDrawRef(self, ...) |
| 54 | +end |
| 55 | + |
| 56 | +-- |
| 57 | + |
| 58 | +local uieDrawChildrenRef = UIElement.draw_children |
| 59 | +function UIElement:draw_children(...) |
| 60 | + local stenciled = false |
| 61 | + if self.states.visible and self.config and self.config.no_overflow then |
| 62 | + -- draw stencil for overflow container |
| 63 | + stenciled = true |
| 64 | + SMODS.push_to_stencil_stack(function(exit) |
| 65 | + prep_draw(self, 1) |
| 66 | + love.graphics.scale(1 / G.TILESIZE) |
| 67 | + love.graphics.setColor(0, 0, 0, 1) |
| 68 | + |
| 69 | + if self.config.r and self.VT.w > 0.01 then |
| 70 | + self:draw_pixellated_rect("fill", 0) |
| 71 | + else |
| 72 | + love.graphics.rectangle("fill", 0, 0, self.VT.w * G.TILESIZE, self.VT.h * G.TILESIZE) |
| 73 | + end |
| 74 | + |
| 75 | + love.graphics.pop() |
| 76 | + end) |
| 77 | + end |
| 78 | + uieDrawChildrenRef(self, ...) |
| 79 | + -- cancel stencil for overflow container |
| 80 | + if stenciled then SMODS.pop_from_stencil_stack() end |
| 81 | +end |
| 82 | + |
| 83 | + |
| 84 | +-- collision check |
| 85 | +function Node:inside_overflow_boundaries(point) |
| 86 | + -- Use cached value if present for current point |
| 87 | + if self.overflow_check_timer == G.TIMERS.REAL and self.overflow_check_point == point then |
| 88 | + return self.overflow_check_result or false |
| 89 | + end |
| 90 | + self.overflow_check_timer = G.TIMERS.REAL |
| 91 | + self.overflow_check_point = point |
| 92 | + local r = true |
| 93 | + |
| 94 | + -- No parent = no overflow can be done so collide as usual |
| 95 | + if not self.parent then r = true |
| 96 | + -- If parent has overflow then we should check do we collide with it and if not, all children in it cannot be collided too |
| 97 | + elseif self.parent.config and self.parent.config.no_overflow and not Node.collides_with_point(self.parent, point) then r = false |
| 98 | + -- Otherwise process all parents looking for first non-collideable overflow |
| 99 | + else r = Node.inside_overflow_boundaries(self.parent, point) end |
| 100 | + |
| 101 | + self.overflow_check_result = r |
| 102 | + return r |
| 103 | +end |
| 104 | + |
| 105 | +-- |
| 106 | + |
| 107 | +SMODS.UIScrollBox = UIBox:extend() |
| 108 | +function SMODS.UIScrollBox:init(args) |
| 109 | + args = SMODS.merge_defaults(args, { content = {}, container = {}, overflow = {}, sync_mode = "progress" }) |
| 110 | + args.progress = SMODS.merge_defaults(args.progress, { x = 0, y = 0 }) |
| 111 | + args.offset = SMODS.merge_defaults(args.offset, { x = 0, y = 0 }) |
| 112 | + |
| 113 | + self.scroll_args = args |
| 114 | + self.scroll_progress = args.progress |
| 115 | + self.scroll_offset = args.offset |
| 116 | + self.scroll_sync_mode = args.sync_mode |
| 117 | + |
| 118 | + if args.content and args.content.is and args.content:is(Object) then |
| 119 | + self.content = args.content |
| 120 | + else |
| 121 | + self.content = UIBox(args.content) |
| 122 | + end |
| 123 | + |
| 124 | + args.container.config = SMODS.merge_defaults(args.container.config, { align = "cm", offset = { x = 0, y = 0 } }) |
| 125 | + args.container.node_config = SMODS.merge_defaults(args.container.node_config, { colour = G.C.CLEAR }) |
| 126 | + args.container.definition = { |
| 127 | + n = G.UIT.ROOT, |
| 128 | + config = args.container.node_config, |
| 129 | + nodes = { |
| 130 | + { |
| 131 | + n = G.UIT.O, |
| 132 | + config = { |
| 133 | + object = self.content, |
| 134 | + }, |
| 135 | + }, |
| 136 | + }, |
| 137 | + } |
| 138 | + self.content_container = UIBox(args.container) |
| 139 | + |
| 140 | + args.overflow.config = SMODS.merge_defaults(args.overflow.config, {}) |
| 141 | + args.overflow.node_config = SMODS.merge_defaults(args.overflow.node_config, { colour = G.C.CLEAR, no_overflow = true }) |
| 142 | + args.overflow.definition = { |
| 143 | + n = G.UIT.ROOT, |
| 144 | + config = args.overflow.node_config, |
| 145 | + nodes = { |
| 146 | + { |
| 147 | + n = G.UIT.O, |
| 148 | + config = { |
| 149 | + object = self.content_container, |
| 150 | + }, |
| 151 | + }, |
| 152 | + }, |
| 153 | + } |
| 154 | + |
| 155 | + UIBox.init(self, args.overflow) |
| 156 | + |
| 157 | + self:sync_scroll(0, true) |
| 158 | +end |
| 159 | +function SMODS.UIScrollBox:get_scroll_distance() |
| 160 | + return math.max(0, self.content_container.T.w - self.T.w), math.max(0, self.content_container.T.h - self.T.h) |
| 161 | +end |
| 162 | +function SMODS.UIScrollBox:sync_scroll_offset() |
| 163 | + local dx, dy = self:get_scroll_distance() |
| 164 | + self.scroll_offset.x = dx * (self.scroll_progress.x or 0) |
| 165 | + self.scroll_offset.y = dy * (self.scroll_progress.y or 0) |
| 166 | +end |
| 167 | +function SMODS.UIScrollBox:sync_scroll_progress() |
| 168 | + local dx, dy = self:get_scroll_distance() |
| 169 | + self.scroll_progress.x = (dx == 0 and 0) or ((self.offset.x or 0) / dx) |
| 170 | + self.scroll_progress.y = (dy == 0 and 0) or ((self.offset.y or 0) / dy) |
| 171 | +end |
| 172 | +function SMODS.UIScrollBox:set_scroll_offset(t) |
| 173 | + self.scroll_offset = SMODS.merge_defaults(t, { x = 0, y = 0 }) |
| 174 | + self:sync_scroll_progress() |
| 175 | +end |
| 176 | +function SMODS.UIScrollBox:set_scroll_progress(t) |
| 177 | + self.scroll_progress = SMODS.merge_defaults(t, { x = 0, y = 0 }) |
| 178 | + self:sync_scroll_offset() |
| 179 | +end |
| 180 | +function SMODS.UIScrollBox:sync_scroll(dt, init) |
| 181 | + if self.scroll_sync_mode == "progress" then |
| 182 | + self:sync_scroll_offset() |
| 183 | + elseif self.scroll_sync_mode == "offset" then |
| 184 | + self:sync_scroll_progress() |
| 185 | + end |
| 186 | + self.content_container.config.offset.x = -(self.scroll_offset.x or 0) |
| 187 | + self.content_container.config.offset.y = -(self.scroll_offset.y or 0) |
| 188 | +end |
| 189 | +function SMODS.UIScrollBox:update(dt) |
| 190 | + if self.scroll_args.scroll_move then |
| 191 | + self.scroll_args.scroll_move(self, dt) |
| 192 | + end |
| 193 | + self:sync_scroll(dt) |
| 194 | + UIBox.update(self, dt) |
| 195 | +end |
| 196 | + |
| 197 | +-- |
| 198 | + |
4 | 199 | function STR_UNPACK(str) |
5 | 200 | local chunk, err = loadstring(str) |
6 | 201 | if chunk then |
|
0 commit comments