element.lua
1 -- File: element.lua 2 3 --[[ 4 Copyright (C) 2024 chmod777 5 6 This program is free software: you can redistribute it and/or modify it under 7 the terms of the GNU Affero General Public License version 3 as published by the 8 Free Software Foundation. 9 10 This program is distributed in the hope that it will be useful, but WITHOUT ANY 11 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 12 PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 13 14 You should have received a copy of the GNU Affero General Public License along 15 with this program. If not, see <https://www.gnu.org/licenses/>. 16 ]] 17 18 local luaWidgetDir = 'LuaUI/Widgets/' 19 20 local Length = VFS.Include(luaWidgetDir..'chmod777_includes/html_css/css_property_parser.lua').Length 21 22 local Initial = { 23 ['display'] = 'block', 24 ['box-sizing'] = 'content-box', 25 26 ['width'] = 'auto', 27 ['min-width'] = 'auto', 28 ['max-width'] = 'none', 29 30 ['height'] = 'auto', 31 ['min-height'] = 'auto', 32 ['max-height'] = 'none', 33 34 ['padding-top'] = Length:new(0, "px"), 35 ['padding-right'] = Length:new(0, "px"), 36 ['padding-bottom'] = Length:new(0, "px"), 37 ['padding-left'] = Length:new(0, "px"), 38 39 ['border-top-width'] = Length:new(0, "px"), 40 ['border-right-width'] = Length:new(0, "px"), 41 ['border-bottom-width'] = Length:new(0, "px"), 42 ['border-left-width'] = Length:new(0, "px"), 43 44 ['margin-top'] = Length:new(0, "px"), 45 ['margin-right'] = Length:new(0, "px"), 46 ['margin-bottom'] = Length:new(0, "px"), 47 ['margin-left'] = Length:new(0, "px"), 48 49 -- ['background-color'] = Color['transparent'](), 50 -- ['background-image'] = 'none', 51 -- ['background-size'] = BackgroundSize:new(), 52 -- ['background-position'] = BackgroundPosition:new(), 53 -- ['background-repeat'] = BackgroundRepeat:new(), 54 -- ['background-blend-mode'] = 'normal', 55 56 ['div'] = {}, 57 ['span'] = { 58 ['display'] = 'inline', 59 }, 60 } 61 62 local Element = {} 63 function Element:new(tag, attributes, children, text) 64 if tag == nil then tag = 'root' end 65 if attributes == nil then attributes = {} end 66 if children == nil then children = {} end 67 if text ~= nil then text = text:gsub("%s+$", "") end 68 local this = { 69 tag = tag, 70 attributes = attributes, 71 children = children, 72 text = text, 73 rulesets = {}, 74 } 75 function this:link_elements(parent, previous_sibling, next_sibling) 76 this.parent = parent 77 this.previous_sibling = previous_sibling 78 this.next_sibling = next_sibling 79 for i,child in ipairs(children) do 80 child:link_elements(this, this.children[i-1], this.children[i+1]) 81 end 82 end 83 84 function this:build_style_tree(stylesheet) 85 local style_tree = this 86 for r,ruleset in ipairs(stylesheet) do 87 local selector_group = ruleset.selector_group 88 local elements = style_tree:get_elements_by_selector_group(selector_group) 89 for e,element in ipairs(elements) do 90 element:add_ruleset(ruleset) 91 ruleset:add_element(element) 92 end 93 end 94 return style_tree 95 end 96 function this:add_ruleset(new_ruleset) 97 local rulesets = this.rulesets 98 for r,ruleset in pairs(rulesets) do 99 if new_ruleset == ruleset then return end 100 end 101 this.rulesets[#this.rulesets+1] = new_ruleset 102 end 103 function this:get_elements_by_selector_group(selector_group) 104 local elements = {} 105 for g,selector in ipairs(selector_group) do 106 this:elements_by_selector(elements, selector, 1, g) 107 end 108 for c,child in ipairs(this.children) do 109 local child_matches = child:get_elements_by_selector_group(selector_group) 110 for i,child_match in ipairs(child_matches) do 111 child_match:add_this_to_elements(elements) 112 end 113 end 114 return elements 115 end 116 function this:elements_by_selector(elements, selector, current_i, group) 117 local current_selector = selector[current_i] 118 local current_type = current_selector.type 119 120 local current_selector_matches = this:simple_selector_matches(current_selector) 121 local next_selector = selector[current_i+1] 122 if current_selector_matches and next_selector then 123 local combinator = current_selector.combinator 124 if combinator == "SPACE" then -- descendant 125 for child in ipairs(this.children) do 126 Spring.Echo("Descendant combinator not implemented") 127 end 128 elseif combinator == ">" then -- child 129 this:child_by_selector(elements, selector, current_i, group) 130 elseif combinator == "+" then -- next-sibling 131 this:next_sibling_by_selector(elements, selector, current_i, group) 132 elseif combinator == "~" then -- subsequent-sibling 133 this:subsequent_siblings_by_selector(elements, selector, current_i, group) 134 else 135 Spring.Echo("Unknown combinator!") 136 end 137 elseif current_selector_matches then -- and not next_selector 138 this:add_this_to_elements(elements) 139 elseif next_selector then -- and not current_selector_matches 140 else -- not current_selector_matches and not next_selector 141 end 142 end 143 function this:simple_selector_matches(simple_selector) 144 local selector_type = simple_selector.type 145 if selector_type == "univeral" then 146 return true 147 elseif selector_type == "type selector" then 148 return this.tag == simple_selector.name 149 elseif selector_type == "hash" then 150 return this.attributes["id"] == simple_selector.name 151 elseif selector_type == "class" then 152 return this.attributes["class"] == simple_selector.name 153 else 154 Spring.Echo("Selector "..selector_type.." not implemented!") 155 return false 156 end 157 end 158 function this:add_this_to_elements(elements) 159 -- Don't add duplicates 160 for i=1, #elements do 161 if this == elements[i] then return end 162 end 163 elements[#elements+1] = this 164 end 165 function this:child_by_selector(elements, selector, current_i, group) 166 local next_selector = selector[current_i+1] 167 for c,child in ipairs(this.children) do 168 if child:simple_selector_matches(next_selector) then 169 child:elements_by_selector(elements, selector, current_i+1, group) 170 end 171 end 172 end 173 function this:next_sibling_by_selector(elements, selector, current_i, group) 174 local next_selector = selector[current_i+1] 175 local next_sibling = this.next_sibling 176 while next_sibling ~= nil do 177 if next_sibling:simple_selector_matches(next_selector) then 178 next_sibling:elements_by_selector(elements, selector, current_i+1, group) 179 break 180 end 181 next_sibling = next_sibling.next_sibling 182 end 183 end 184 function this:subsequent_siblings_by_selector(elements, selector, current_i, group) 185 local next_selector = selector[current_i+1] 186 local next_sibling = this.next_sibling 187 while next_sibling ~= nil do 188 if next_sibling:simple_selector_matches(next_selector) then 189 next_sibling:elements_by_selector(elements, selector, current_i+1, group) 190 end 191 next_sibling = next_sibling.next_sibling 192 end 193 end 194 195 function this:get_declaration(property_name) 196 local value 197 for r,ruleset in ipairs(this.rulesets) do 198 for d,declaration in ipairs(ruleset) do 199 if property_name == declaration.property then 200 value = declaration.value 201 end 202 end 203 end 204 return value 205 end 206 function this:get_initial(property_name) 207 return Initial[this.tag][property_name] 208 end 209 210 function this:get_value_or(property_name, default) 211 local declaration = this:get_declaration(property_name) 212 if declaration == nil then 213 return default 214 end 215 if type(declaration) == "table" then 216 return declaration.value 217 end 218 return declaration 219 end 220 function this:get_absolute_value_or(property_name, default, context) 221 local declaration = this:get_declaration(property_name) 222 if declaration == nil then 223 return default 224 end 225 return declaration.value:as_px(context).value 226 end 227 228 function this:print(tabs) 229 if tabs == nil then tabs = 0 end 230 local prefix = "" 231 for tab=1, tabs do 232 prefix = prefix.."\t" 233 end 234 Spring.Echo(prefix.."{") 235 Spring.Echo(prefix.."tag:", this.tag) 236 Spring.Echo(prefix.."text:", this.text) 237 Spring.Echo(prefix.."attributes:") 238 for k,v in pairs(this.attributes) do 239 Spring.Echo("\t"..prefix..k,v) 240 end 241 Spring.Echo(prefix.."children:") 242 for k,v in pairs(this.children) do 243 v:print(tabs+1) 244 end 245 Spring.Echo(prefix.."rules") 246 for k,v in pairs(this.rulesets) do 247 for k,v in pairs(v) do 248 Spring.Echo(k,v.property, v.value) 249 end 250 end 251 252 Spring.Echo(prefix.."}") 253 end 254 255 return this 256 end 257 258 return { 259 Initial = Initial, 260 Element = Element, 261 }