diff options
Diffstat (limited to 'src/syslua/lx/tk.lua')
-rw-r--r-- | src/syslua/lx/tk.lua | 415 |
1 files changed, 304 insertions, 111 deletions
diff --git a/src/syslua/lx/tk.lua b/src/syslua/lx/tk.lua index 5653313..473ae3f 100644 --- a/src/syslua/lx/tk.lua +++ b/src/syslua/lx/tk.lua @@ -104,12 +104,15 @@ function region_diff(reg1, reg2) end function region_inter(reg1, reg2) - return region_operation(reg1, reg2, - function(x0, x1, y0, y1) - return (x0 >= reg1.x and x0 < reg1.x + reg1.w and y0 >= reg1.y and y0 < reg1.y + reg1.h) - and (x0 >= reg2.x and x0 < reg2.x + reg2.w and y0 >= reg2.y and y0 < reg2.y + reg2.h) - end - ) + local x0 = math.max(reg1.x, reg2.x) + local x1 = math.min(reg1.x + reg1.w, reg2.x + reg2.w) + local y0 = math.max(reg1.y, reg2.y) + local y1 = math.min(reg1.y + reg1.h, reg2.y + reg2.h) + if x1 > x0 and y1 > y0 then + return {x = x0, y = y0, w = x1 - x0, h = y1 - y0} + else + return nil + end end function region_union(reg1, reg2) @@ -139,37 +142,64 @@ function tk.widget(width, height, opts) end end - function w:resize(width, height) - if width then w.width = width end - if width then w.height = height end + -- Funcions that must be implemented by a parent widget : + -- - resize_child : try to resize a child widget, checks if can be resized, + -- does all the required redrawing + -- - redraw : redraws a portion of the widget + + -- can resize : checks if dimensiosn are valid + -- widget can replace this if necessary + function w:can_resize(width, height) + return (not width or width > 0) and (not height or height > 0) end - function w:get_draw_buffer(x0, y0, w, h) - -- Replaced by parent by a function that returns the buffer for - return nil + -- do resize : updates position and size values but does not redraw anything + -- widget must replace this when necessary ! + function w:do_resize(width, height) + if width then self.width = width end + if height then self.height = height end + end + + -- resize : calls all the necessary procedures for resizing and redrawing + -- widget must not replace this ! + function w:resize(width, height) + if self.parent then + self.parent:resize_child(self, width, height) + end end - function w:finalize_draw_buffer() - if self.parent ~= nil then - self.parent:finalize_draw_buffer() + + -- redraw : calls parent to redraw a portion of screen + -- can be replaced by widget to avoid redrawing where hidden things are + function w:redraw(x0, y0, w, h, who) + if self.parent then + self.parent:redraw(x0 + self.x, y0 + self.y, w, h, self) end end - function w:redraw(x0, y0, buf) - -- Replaced by widget code by a function that does the actual drawing + -- draw : does the actual drawing on a buffer + -- must be replaced by widget by code that does the drawing + function w:draw(x0, y0, buf) end - function w:redraw_sub(x0, y0, buf, subwidget) + -- helper function to redraw subwidget + -- must not be replaced by child + function w:draw_sub(x0, y0, buf, subwidget) local region = {x = x0, y = y0, w = buf:width(), h = buf:height()} local subregion = {x = subwidget.x, y = subwidget.y, w = subwidget.width, h = subwidget.height} local inter = region_inter(region, subregion) - for _, z in pairs(inter) do - subwidget:redraw(z.x - subwidget.x, z.y - subwidget.y, buf:sub(z.x - x0, z.y - y0, z.w, z.h)) + if inter then + subwidget:draw(inter.x - subwidget.x, inter.y - subwidget.y, buf:sub(inter.x - x0, inter.y - y0, inter.w, inter.h)) end end + function w:get_child(x, y) + -- Replaced by widget by code that finds a child widget at given position + return nil + end + function w:on_mouse_down(x, y, lb, rb, mb) - if lb then -- Handler for mouse down event + if lb then self.left_click_valid = true end end @@ -222,7 +252,7 @@ function tk.init(gui, root_widget) return gui.surface:rgb(r, g, b) end - root_widget:resize(gui.surface:width(), gui.surface:height()) + root_widget:do_resize(gui.surface:width(), gui.surface:height()) gui.on_key_down = function(key) root_widget:on_key_down(key) end gui.on_key_up = function(key) root_widget:on_key_up(key) end @@ -231,24 +261,34 @@ function tk.init(gui, root_widget) gui.on_mouse_down = function(lb, rb, mb) root_widget:on_mouse_down(gui.mouse_x, gui.mouse_y, lb, rb, mb) end gui.on_mouse_up = function(lb, rb, mb) root_widget:on_mouse_up(gui.mouse_x, gui.mouse_y, lb, rb, mb) end - function root_widget:get_draw_buffer(x0, y0, w, h) - if gui.cursor_visible and #region_inter({x=x0, y=y0, w=w, h=h}, - {x=gui.mouse_x, y=gui.mouse_y, w=gui.cursor:width(), h=gui.cursor:height()})>0 - then - tk.must_reshow_cursor_on_finalize = true - gui.hide_cursor() - end - return gui.surface:sub(x0, y0, w, h) - end - function root_widget:finalize_draw_buffer() - if tk.must_reshow_cursor_on_finalize then - tk.must_reshow_cursor_on_finalize = false - gui.show_cursor() + local root_parent = {} + function root_parent:resize_child(c) + -- NO ! Do nothing. + end + function root_parent:redraw(x0, y0, w, h) + local ss = region_inter({x = x0, y = y0, w = w, h = h}, + {x = 0, y = 0, w = gui.surface:width(), h = gui.surface:height()}) + + if ss then + local must_reshow_cursor = false + if gui.cursor_visible and region_inter(ss, {x=gui.mouse_x, y=gui.mouse_y, + w=gui.cursor:width(), h=gui.cursor:height()}) + then + must_reshow_cursor = true + gui.hide_cursor() + end + + local buf = gui.surface:sub(ss.x, ss.y, ss.w, ss.h) + root_widget:draw(ss.x, ss.y, buf) + + if must_reshow_cursor then + gui.show_cursor() + end end end - root_widget:redraw(0, 0, root_widget:get_draw_buffer(0, 0, root_widget.width, root_widget.height)) - root_widget:finalize_draw_buffer() + root_widget.parent = root_parent + root_parent:redraw(0, 0, root_widget.width, root_widget.height) end -- #### #### STANDARD WIDGETS #### #### @@ -260,18 +300,11 @@ function tk.image(a, b) local image = tk.widget(img:width(), img:height(), opts) image.img = img - function image:resize(width, height) - if width then - self.width = width - if self.width < self.img:width() then self.width = self.img:width() end - end - if height then - self.height = height - if self.height < self.img:height() then self.height = self.img:height() end - end + function image:can_resize(width, height) + return width >= self.img:width() and height >= self.img:height() end - function image:redraw(x0, y0, buf) + function image:draw(x0, y0, buf) local step = 20 local halfstep = 10 for x = x0 - (x0 % step), x0 + buf:width(), step do @@ -307,13 +340,13 @@ function tk.text(a, b) local txt = tk.widget(100, #string.split(text, "\n")*(opts.text_size + opts.line_spacing) - opts.line_spacing + 2*opts.padding, opts) txt.text = text - function txt:redraw(x0, y0, buf) + function txt:draw(x0, y0, buf) local lines = string.split(self.text, "\n") buf:fillrect(0, 0, buf:width(), buf:height(), self.background) - local acceptable_surface = region_inter({x = x0, y = y0, w = buf:width(), h = buf:height()}, - {x = self.padding, y = self.padding, w = self.width - 2*self.padding, h = self.height - 2*self.padding}) - for _, s in pairs(acceptable_surface) do + local s = region_inter({x = x0, y = y0, w = buf:width(), h = buf:height()}, + {x = self.padding, y = self.padding, w = self.width - 2*self.padding, h = self.height - 2*self.padding}) + if s then local buf2 = buf:sub(s.x - x0, s.y - y0, s.w, s.h) for i = 1, #lines do -- TODO word wrap @@ -335,13 +368,16 @@ function tk.box(a, b) opts.vresize = opts.vresize or false opts.hscroll = opts.hscroll or false opts.vscroll = opts.vscroll or false + opts.min_width = opts.min_width or 8 + opts.min_height = opts.min_height or 8 opts.center_content = opts.center_content or false opts.constrain_size = opts.constrain_size or nil opts.background_color = opts.background_color or tk.rgb(190, 190, 190) - opts.control_size = 12 + opts.control_size = opts.control_size or 12 local box = tk.widget(content.width, content.height, opts) box.content = content + box.content.parent = box if box.center_content then if box.width > box.content.width then box.content.x = (box.width - box.content.width) // 2 @@ -358,30 +394,51 @@ function tk.box(a, b) box.content.y = 0 end - function box:resize(width, height) - local ow, oh = self.width, self.height + function box:can_resize(width, height) if self.constrain_size then - self.content:resize(width, height) - self.width = content.width - self.height = content.height + return self.content:can_resize(width, height) + else + if width then + if self.min_width and width < self.min_width then return false end + end + if height then + if self.min_height and height < self.min_height then return false end + end + return true + end + end + + function box:do_resize(width, height) + if self.constrain_size then + self.content:do_resize(width, height) + self.width = self.content.width + self.height = self.content.height else if width then self.width = width end if height then self.height = height end - if self.center_content then - if self.width > self.content.width then - self.content.x = (self.width - self.content.width) // 2 - else - self.content.x = math.min(0, math.max(-(self.content.width - self.width), self.content.x)) - end - if self.height > self.content.height then - self.content.y = (self.height - self.content.height) // 2 - else - self.content.y = math.min(0, math.max(-(self.content.height - self.height), self.content.y)) - end + end + if self.center_content then + if self.width > self.content.width then + self.content.x = (self.width - self.content.width) // 2 + else + self.content.x = math.min(0, math.max(-(self.content.width - self.width), self.content.x)) + end + if self.height > self.content.height then + self.content.y = (self.height - self.content.height) // 2 + else + self.content.y = math.min(0, math.max(-(self.content.height - self.height), self.content.y)) end end - if self.parent and (self.width ~= ow or self.height ~= oh) then - self.parent:child_resized(self) + end + + function box:resize_child(c, w, h) + assert(c == self.content) + if self.constrain_size then + self:resize(w, h) + elseif self.content:can_resize(w, h) then + self.content:do_resize(w, h) + self:do_resize(self.width, self.height) + self:redraw(0, 0, self.width, self.height, self) end end @@ -390,7 +447,7 @@ function tk.box(a, b) if x then self.content.x = math.min(0, math.max(-(self.content.width - self.width), x)) end if y then self.content.y = math.min(0, math.max(-(self.content.height - self.height), y)) end if self.parent and (self.content.x ~= ox or self.content.y ~= oy) then - self.parent:child_resized(self) + self:redraw(0, 0, self.width, self.height, self) end end @@ -402,15 +459,15 @@ function tk.box(a, b) return barsize, barpos, baravail end - function box:redraw(x0, y0, buf) + function box:draw(x0, y0, buf) local csz = self.control_size - local acceptable_surface = region_inter({x = x0, y = y0, w = buf:width(), h = buf:height()}, + local s = region_inter({x = x0, y = y0, w = buf:width(), h = buf:height()}, {x = self.content.x, y = self.content.y, w = self.content.width, h = self.content.height}) - for _, s in pairs(acceptable_surface) do + if s then local buf2 = buf:sub(s.x - x0, s.y - y0, s.w, s.h) - self.content:redraw(s.x - self.content.x, s.y - self.content.y, buf2) + self.content:draw(s.x - self.content.x, s.y - self.content.y, buf2) end local background_surface = region_diff({x = x0, y = y0, w = buf:width(), h = buf:height()}, {x = self.content.x, y = self.content.y, @@ -502,6 +559,154 @@ function tk.box(a, b) end +function tk.grid(a, b) + local contents = b or a + local opts = b and a or {} + + opts.border_size = 0 + opts.border_color = tk.rgb(200, 200, 200) + + local grid = tk.widget(nil, nil, opts) + grid.contents = contents + + function grid:can_resize(w, h) + return w == self.col_pos[#self.contents[1]+1] and h == self.line_pos[#self.contents+1] + end + + function grid:reposition_elements() + self.line_height = {} + self.col_width = {} + self.line_pos = {0} + self.col_pos = {0} + -- Recalculate line height + for l = 1, #self.contents do + self.line_height[l] = 0 + for c = 1, #self.contents[l] do + self.line_height[l] = math.max(self.contents[l][c].height, self.line_height[l]) + end + self.line_pos[l + 1] = self.line_pos[l] + self.line_height[l] + self.border_size + end + -- Recalculate column height + for c = 1, #self.contents[1] do + self.col_width[c] = 0 + for l = 1, #self.contents do + self.col_width[c] = math.max(self.contents[l][c].width, self.col_width[c]) + end + self.col_pos[c + 1] = self.col_pos[c] + self.col_width[c] + self.border_size + end + -- Reposition elements + for l = 1, #self.contents do + for c = 1, #self.contents[l] do + self.contents[l][c].parent = self + self.contents[l][c].grid_l = l + self.contents[l][c].grid_c = c + self.contents[l][c].x = self.col_pos[c] + self.contents[l][c].y = self.line_pos[l] + self.contents[l][c]:do_resize(self.col_width[c], self.line_height[l]) + end + end + -- Resize me + self:do_resize(self.col_pos[#self.contents[1]+1], self.line_pos[#self.contents+1]) + end + + function grid:get_lc(x, y) + function dichotomy(tab, val, i0, i1) + if i0 == i1 then + return i0 + elseif i1 == i0 + 1 then + if tab[i1] <= val then + return i1 + else + return i0 + end + else + local mid = (i0 + i1) // 2 + if tab[mid] <= val then + return dichotomy(tab, val, mid, i1) + else + return dichotomy(tab, val, i0, mid - 1) + end + end + end + return dichotomy(self.line_pos, y, 1, #self.contents), + dichotomy(self.col_pos, x, 1, #self.contents[1]) + end + + function grid:draw(x0, y0, buf) + local l0, c0 = self:get_lc(x0, y0) + local l1, c1 = self:get_lc(x0 + buf:width(), y0 + buf:height()) + + for l = l0, l1 do + for c = c0, c1 do + self:draw_sub(x0, y0, buf, self.contents[l][c]) + end + end + end + + function grid:resize_child(item, width, height) + if not item:can_resize(width, height) then return end + + local l, c = item.grid_l, item.grid_c + local recalc = false + if width and width ~= self.col_width[c] then + for l1 = 1, #self.contents do + self.contents[l1][c]:do_resize(width, nil) + end + recalc = true + end + if height and height ~= self.line_height[l] then + for c1 = 1, #self.contents[l] do + self.contents[l][c1]:do_resize(nil, height) + end + recalc = true + end + if recalc then + self:reposition_elements() + end + self:resize(self.width, self.height) + end + + function grid:on_mouse_down(x, y, lb, rb, mb) + local l, c = self:get_lc(x, y) + local it = self.contents[l][c] + + self.mouse_lb = self.mouse_lb or lb + self.mouse_rb = self.mouse_rb or rb + self.mouse_mb = self.mouse_mb or mb + + if self.mouse_on then + it = self.mouse_on + else + self.mouse_on = it + end + it:on_mouse_down(x - it.x, y - it.y, lb, mb, rb) + end + + function grid:on_mouse_up(x, y, lb, rb, mb) + local l, c = self:get_lc(x, y) + local it = self.mouse_on or self.contents[l][c] + it:on_mouse_up(x - it.x, y - it.y, lb, mb, rb) + + self.mouse_lb = self.mouse_lb and not lb + self.mouse_rb = self.mouse_rb and not rb + self.mouse_mb = self.mouse_mb and not mb + if not (self.mouse_lb or self.mouse_mb or self.mouse_rb) then + self.mouse_on = nil + end + end + + function grid:on_mouse_move(prev_x, prev_y, new_x, new_y) + local l, c = self:get_lc(prev_x, prev_y) + local it = self.mouse_on or self.contents[l][c] + it:on_mouse_move(prev_x - it.x, prev_y - it.y, new_x - it.x, new_y - it.y) + end + + grid:reposition_elements() + + return grid +end + + -- #### #### WINDOW MANAGER WIDGET #### #### function tk.window_manager() @@ -529,32 +734,28 @@ function tk.window_manager() content.x = 1 content.y = 21 - function content:get_draw_buffer(x0, y0, w, h) - if x0 + w > self.width then w = self.width - x0 end - if y0 + h > self.height then h = self.height - y0 end - return win:get_draw_buffer(x0 + 1, y0 + 21, w, h) - end + function win:resize_child(c, w, h) + assert(c == self.content) - function win:get_draw_buffer(x0, y0, w, h) - -- TODO clipping etc - end + if c:can_resize(w, h) then + local reg1 = wm.window_pos[self] - function win:child_resized(c) - assert(c == self.content) + c:do_resize(w, h) + self.width = c.width + 2 + self.height = c.height + 22 + + wm.window_pos[self] = {x = self.x, y = self.y, w = self.width, h = self.height} + local reg2 = wm.window_pos[self] - local reg1 = wm.window_pos[self] - self.width = c.width + 2 - self.height = c.height + 22 - wm.window_pos[self] = {x = self.x, y = self.y, w = self.width, h = self.height} - local reg2 = wm.window_pos[self] - local pieces = region_union(reg1, reg2) - for _, p in pairs(pieces) do - wm:redraw_region(p.x, p.y, p.w, p.h) + local pieces = region_union(reg1, reg2) + for _, p in pairs(pieces) do + wm:redraw(p.x, p.y, p.w, p.h) + end end end - function win:redraw(x0, y0, buf) - self:redraw_sub(x0, y0, buf, self.content) + function win:draw(x0, y0, buf) + self:draw_sub(x0, y0, buf, self.content) buf:rect(-x0, -y0, self.width, self.height, buf:rgb(255, 128, 0)) buf:fillrect(-x0+1, -y0+1, win.width-2, 20, buf:rgb(200, 200, 200)) @@ -588,7 +789,7 @@ function tk.window_manager() local reg2 = wm.window_pos[self] local pieces = region_union(reg1, reg2) for _, p in pairs(pieces) do - wm:redraw_region(p.x, p.y, p.w, p.h) + wm:redraw(p.x, p.y, p.w, p.h) end else self.content:on_mouse_move(px-1, py-21, nx-1, ny-21) @@ -609,29 +810,21 @@ function tk.window_manager() for i, w2 in pairs(self.windows) do if w2 == win then table.remove(self.windows, i) - return + break end end - end + self.window_pos[win] = nil - function wm:redraw_region(x, y, w, h) - local ok = region_inter({x = 0, y = 0, w = self.width, h = self.height}, - {x = x, y = y, w = w, h = h}) - for _, r in pairs(ok) do - self:redraw(r.x, r.y, self:get_draw_buffer(r.x, r.y, r.w, r.h)) - self:finalize_draw_buffer() - end + self:redraw_win(win) end function wm:redraw_win(win) - self:redraw_region(win.x, win.y, win.width, win.height) + self:redraw(win.x, win.y, win.width, win.height) end - function wm:redraw(x0, y0, buf) + function wm:draw(x0, y0, buf) local remaining = { {x = x0, y = y0, w = buf:width(), h = buf:height()} } - -- TODO do this in inverse order and clip regions of hidden windows - -- so that less redrawing is done for i = #self.windows, 1, -1 do win = self.windows[i] if win.visible then @@ -640,8 +833,8 @@ function tk.window_manager() for _, reg in pairs(remaining) do local draw_to = region_inter(reg, win_reg) - for _, reg2 in pairs(draw_to) do - win:redraw(reg2.x - win.x, reg2.y - win.y, buf:sub(reg2.x - x0, reg2.y - y0, reg2.w, reg2.h)) + if draw_to then + win:draw(draw_to.x - win.x, draw_to.y - win.y, buf:sub(draw_to.x - x0, draw_to.y - y0, draw_to.w, draw_to.h)) end local remain_to = region_diff(reg, win_reg) |