layout.lua
1 -- File: layout.lua 2 3 -- RESOURCES 4 -- https://www.w3.org/TR/2011/REC-CSS2-20110607/#minitoc 5 -- https://github.com/tensor-programming/rust_browser_part_4/blob/master/src/layout.rs 6 7 local luaWidgetDir = 'LuaUI/Widgets/' 8 9 local Element = VFS.Include(luaWidgetDir..'chmod777_includes/html_css/element.lua') 10 11 local Rect = {} 12 function Rect:new(x, y, width, height) 13 if x == nil then x = 0 end 14 if y == nil then y = 0 end 15 if width == nil then width = 0 end 16 if height == nil then height = 0 end 17 18 local this = { 19 x = x, 20 y = y, 21 width = width, 22 height = height, 23 } 24 function this:expand(edge_size) 25 local new_x = this.x - edge_size.left 26 local new_y = this.y - edge_size.top 27 local new_width = this.width + edge_size.left + edge_size.right 28 local new_height = this.height + edge_size.top + edge_size.bottom 29 return Rect:new(new_x, new_y, new_width, new_height) 30 end 31 32 function this:print() 33 Spring.Echo("x: "..this.x.." y: "..this.y.." w: "..this.width.." h: "..this.height) 34 end 35 36 return this 37 end 38 39 local EdgeSizes = {} 40 function EdgeSizes:new(top, right, bottom, left) 41 if top == nil then top = 0 end 42 if right == nil then right = 0 end 43 if bottom == nil then bottom = 0 end 44 if left == nil then left = 0 end 45 46 local this = { 47 top = top, 48 right = right, 49 bottom = bottom, 50 left = left, 51 } 52 53 function this:print() 54 Spring.Echo("t: "..this.top.." r: "..this.right.." b: "..this.bottom.." l: "..this.left) 55 end 56 57 return this 58 end 59 60 local BoxType = { 61 "block", 62 "inline", 63 "inline-block", 64 "anonymous", 65 } 66 67 local Dimensions = {} 68 ---@param content Rect 69 ---@param padding EdgeSizes 70 ---@param border EdgeSizes 71 ---@param margin EdgeSizes 72 ---@param current Rect 73 ---@return Dimensions 74 function Dimensions:new(content, padding, border, margin, current) 75 if content == nil then content = Rect:new() end 76 if padding == nil then padding = EdgeSizes:new() end 77 if border == nil then border = EdgeSizes:new() end 78 if margin == nil then margin = EdgeSizes:new() end 79 if current == nil then current = Rect:new() end 80 81 local this = { 82 content = content, 83 padding = padding, 84 border = border, 85 margin = margin, 86 current = current, 87 } 88 89 function this:print() 90 this.content:print() 91 this.padding:print() 92 this.border:print() 93 this.margin:print() 94 end 95 96 ---@return Rect 97 function this:padding_box() 98 return this.content:expand(this.padding) 99 end 100 ---@return Rect 101 function this:border_box() 102 return this:padding_box():expand(this.border) 103 end 104 ---@return Rect 105 function this:margin_box() 106 return this:border_box():expand(this.margin) 107 end 108 return this 109 end 110 111 local Layout = {} 112 function Layout:new(style_node, box_type, dimensions, children) 113 if style_node == nil then end 114 if box_type == nil then box_type = BoxType[1] end 115 if dimensions == nil then dimensions = Dimensions:new() end 116 if children == nil then children = {} end 117 118 local this = { 119 dimensions = dimensions, 120 box_type = box_type, 121 style_node = style_node, 122 children = children, 123 } 124 125 function this:layout(parent_dim) 126 local box_type = this.box_type 127 if box_type == "block" then 128 this:layout_block(parent_dim) 129 elseif box_type == "inline" then 130 this:layout_inline(parent_dim) 131 elseif box_type == "inline-block" then 132 this:layout_inline_block(parent_dim) 133 elseif box_type == "anonymous" then 134 this:layout_anonymous() 135 end 136 end 137 function this:layout_block(parent_dim) 138 this:calculate_width(parent_dim) 139 this:calculate_position(parent_dim) 140 this:layout_children() 141 this:calculate_height(parent_dim) 142 end 143 function this:layout_inline(parent_dim) 144 end 145 function this:layout_inline_block(parent_dim) 146 this:calculate_inline_width(parent_dim) 147 this:calculate_inline_position(parent_dim) 148 this:layout_children() 149 this:calculate_height(parent_dim) 150 end 151 function this:layout_anonymous() 152 end 153 154 function this:calculate_width(parent_dim) 155 local style_node = this.style_node 156 local dimensions = this.dimensions 157 158 local width = style_node:get_absolute_value_or("width", 0, parent_dim.content.width) 159 dimensions.padding.left = style_node:get_absolute_value_or("padding-left", 0) 160 dimensions.padding.right = style_node:get_absolute_value_or("padding-right", 0) 161 dimensions.border.left = style_node:get_absolute_value_or("border-left-width", 0) 162 dimensions.border.right = style_node:get_absolute_value_or("border-right-width", 0) 163 local margin_left = style_node:get_declaration("margin-left", false) 164 local margin_right = style_node:get_declaration("margin-right", false) 165 local margin_left_value = style_node:get_absolute_value_or("margin-left", 0) 166 local margin_right_value = style_node:get_absolute_value_or("margin-right", 0) 167 168 local total = width 169 + dimensions.padding.left 170 + dimensions.padding.right 171 + dimensions.border.left 172 + dimensions.border.right 173 + margin_left_value 174 + margin_right_value 175 176 local underflow = parent_dim.content.width - total 177 178 if width == 0 then 179 if underflow > 0 then 180 dimensions.content.width = underflow 181 dimensions.margin.right = margin_right_value 182 else 183 dimensions.content.width = width 184 dimensions.margin.right = margin_right_value + underflow 185 end 186 dimensions.margin.left = margin_left_value 187 elseif margin_left == nil and margin_right ~= nil then 188 if width ~= 0 then 189 dimensions.content.width = width 190 dimensions.margin.left = underflow 191 dimensions.margin.right = margin_right_value 192 end 193 elseif margin_left ~= nil and margin_right == nil then 194 if width ~= 0 then 195 dimensions.content.width = width 196 dimensions.margin.left = margin_left_value 197 dimensions.margin.right = underflow 198 end 199 elseif margin_left == nil and margin_right == nil then 200 if width ~= 0 then 201 dimensions.content.width = width 202 dimensions.margin.left = underflow / 2 203 dimensions.margin.right = underflow / 2 204 end 205 else 206 dimensions.content.width = width 207 dimensions.margin.left = margin_left_value 208 dimensions.margin.right = margin_right_value + underflow 209 end 210 end 211 function this:calculate_position(parent_dim) 212 local style_node = this.style_node 213 local dimensions = this.dimensions 214 215 dimensions.padding.top = style_node:get_absolute_value_or("padding-top", 0) 216 dimensions.padding.bottom = style_node:get_absolute_value_or("padding-bottom", 0) 217 dimensions.border.top = style_node:get_absolute_value_or("border-top-width", 0) 218 dimensions.border.bottom = style_node:get_absolute_value_or("border-bottom-width", 0) 219 dimensions.margin.top = style_node:get_absolute_value_or("margin-top", 0) 220 dimensions.margin.bottom = style_node:get_absolute_value_or("margin-bottom", 0) 221 222 dimensions.content.x = parent_dim.content.x 223 + dimensions.padding.left 224 + dimensions.border.left 225 + dimensions.margin.left 226 dimensions.content.y = parent_dim.content.height 227 + parent_dim.content.y 228 + dimensions.padding.top 229 + dimensions.border.top 230 + dimensions.margin.top 231 end 232 233 function this:calculate_inline_width(parent_dim) 234 local style_node = this.style_node 235 local dimensions = this.dimensions 236 237 dimensions.content.width = style_node:get_absolute_value_or("width", 0, parent_dim.content.width) 238 239 dimensions.padding.left = style_node:get_absolute_value_or("padding-left", 0) 240 dimensions.padding.right = style_node:get_absolute_value_or("padding-right", 0) 241 242 dimensions.border.left = style_node:get_absolute_value_or("border-left-width", 0) 243 dimensions.border.right = style_node:get_absolute_value_or("border-right-width", 0) 244 245 dimensions.margin.left = style_node:get_absolute_value_or("margin-left", 0) 246 dimensions.margin.right = style_node:get_absolute_value_or("margin-right", 0) 247 end 248 function this:calculate_inline_position(parent_dim) 249 local style_node = this.style_node 250 local dimensions = this.dimensions 251 252 dimensions.padding.top = style_node:get_absolute_value_or("padding-top", 0) 253 dimensions.padding.bottom = style_node:get_absolute_value_or("padding-bottom", 0) 254 255 dimensions.border.top = style_node:get_absolute_value_or("border-top-width", 0) 256 dimensions.border.bottom = style_node:get_absolute_value_or("border-bottom-width", 0) 257 258 dimensions.margin.top = style_node:get_absolute_value_or("margin-top", 0) 259 dimensions.margin.bottom = style_node:get_absolute_value_or("margin-bottom", 0) 260 261 dimensions.content.x = parent_dim.content.x 262 + parent_dim.current.x 263 + dimensions.padding.left 264 + dimensions.border.left 265 + dimensions.margin.left 266 dimensions.content.y = parent_dim.content.height 267 + parent_dim.content.y 268 + dimensions.padding.top 269 + dimensions.border.top 270 + dimensions.margin.top 271 end 272 273 function this:layout_children() 274 local dimensions = this.dimensions 275 local max_child_height = 0 276 local previous_box_type = "block" 277 for c,child in ipairs(this.children) do 278 if previous_box_type == "inline-block" then 279 if child.box_type == "block" then 280 dimensions.content.height = dimensions.content.height + max_child_height 281 dimensions.current.x = 0 282 end 283 end 284 285 child:layout(dimensions) 286 287 local new_height = child.dimensions:margin_box().height 288 if new_height > max_child_height then 289 max_child_height = new_height 290 end 291 292 if child.box_type == "block" then 293 dimensions.content.height = dimensions.content.height + child.dimensions:margin_box().height 294 elseif child.box_type == "inline-block" then 295 dimensions.current.x = dimensions.current.x + child.dimensions:margin_box().width 296 if dimensions.current.x > dimensions.content.width then 297 dimensions.content.height = dimensions.content.height + max_child_height 298 dimensions.current.x = 0 299 child:layout(dimensions) 300 dimensions.current.x = dimensions.current.x + child.dimensions:margin_box().width 301 end 302 end 303 304 previous_box_type = child.box_type 305 end 306 end 307 function this:calculate_height(parent_dim) 308 local style_node = this.style_node 309 local dimensions = this.dimensions 310 local height = style_node:get_absolute_value_or("height", 0, parent_dim.content.height) 311 if height ~= nil then 312 dimensions.content.height = height 313 end 314 end 315 316 return this 317 end 318 319 function layout_tree(root, parent_dim) 320 parent_dim.content.height = 0 321 local root_box = build_layout_tree(root) 322 root_box:layout(parent_dim) 323 return root_box 324 end 325 326 function build_layout_tree(style_node) 327 local display = style_node:get_value_or("display", "block") 328 if display == "none" then 329 display = "anonymous" 330 end 331 Spring.Echo("'"..display.."'") 332 local layout_node = Layout:new(style_node, display) 333 for i,child in ipairs(style_node.children) do 334 local child_display = child:get_value_or("display", "block") 335 if child_display ~= "none" then 336 layout_node.children[#layout_node.children+1] = build_layout_tree(child) 337 end 338 end 339 return layout_node 340 end 341 342 return { 343 Rect = Rect, 344 EdgeSizes = EdgeSizes, 345 Dimensions = Dimensions, 346 Layout = Layout, 347 layout_tree = layout_tree, 348 build_layout_tree = build_layout_tree, 349 }