Commits
Diff
diff --git a/main.go b/main.go
index 91ab414..d21e2c2 100644
--- a/main.go
+++ b/main.go
@@ -44,4 +44,4 @@ type Window struct {
- CSS cstyle.CSS
- document element.Node
- Styles element.Styles
- Scripts scripts.Scripts
+ CSS cstyle.CSS
+ document element.Node
+ Styles element.Styles
+ Scripts scripts.Scripts
@@ -49,2 +49 @@ type Window struct {
- Rerender bool
- shouldStop bool
+ Rerender bool
@@ -73 +72 @@ func (window *Window) Path(path string) {
- createNode(htmlNodes, &window.document, &window.Styles)
+ CreateNode(htmlNodes, &window.document, &window.Styles)
@@ -118,8 +117 @@ func New(adapterFunction *adapter.Adapter, width, height int) Window {
-func (w *Window) Open() {
- for !w.shouldStop {
- w.CSS.Adapter.Render(w.RenderData)
-
- }
-}
-
-func (w *Window) render(doc *element.Node, shelf *library.Shelf) []element.State {
+func (w *Window) Render(doc *element.Node, shelf *library.Shelf) []element.State {
@@ -221,0 +214,2 @@ func open(data *Window) {
+ debug := false
+
@@ -233,0 +228,4 @@ func open(data *Window) {
+ shouldStop := false
+
+ var rd []element.State
+
@@ -264 +262 @@ func open(data *Window) {
- getRenderData(data, &shelf, &monitor)
+ rd = getRenderData(data, &shelf, &monitor)
@@ -268 +266 @@ func open(data *Window) {
- data.shouldStop = true
+ shouldStop = true
@@ -283 +281 @@ func open(data *Window) {
- getRenderData(data, &shelf, &monitor)
+ rd = getRenderData(data, &shelf, &monitor)
@@ -295 +293 @@ func open(data *Window) {
- getRenderData(data, &shelf, &monitor)
+ rd = getRenderData(data, &shelf, &monitor)
@@ -304 +302 @@ func open(data *Window) {
- getRenderData(data, &shelf, &monitor)
+ rd = getRenderData(data, &shelf, &monitor)
@@ -313 +311 @@ func open(data *Window) {
- getRenderData(data, &shelf, &monitor)
+ rd = getRenderData(data, &shelf, &monitor)
@@ -319 +317 @@ func open(data *Window) {
- getRenderData(data, &shelf, &monitor)
+ rd = getRenderData(data, &shelf, &monitor)
@@ -325 +323 @@ func open(data *Window) {
- getRenderData(data, &shelf, &monitor)
+ rd = getRenderData(data, &shelf, &monitor)
@@ -331 +329 @@ func open(data *Window) {
- getRenderData(data, &shelf, &monitor)
+ rd = getRenderData(data, &shelf, &monitor)
@@ -337 +335 @@ func open(data *Window) {
- getRenderData(data, &shelf, &monitor)
+ rd = getRenderData(data, &shelf, &monitor)
@@ -340 +338,15 @@ func open(data *Window) {
- getRenderData(data, &shelf, &monitor)
+ // !ISSUE: the loop should be moved to the adapter and the rerendering should only happen if a eventlistener goes off
+ // + also have a animation loop seperate but thats later
+ // + really should run runevents after get events and if there is any changes then rerender
+ // + ahh what about dom changes in the js api...
+ // + could swap to getters and setters but i don't like them
+ // Main game loop
+ rd = getRenderData(data, &shelf, &monitor)
+ // !TODO: Move to adapter
+
+ for !shouldStop {
+ if !shouldStop && debug {
+ shouldStop = true
+ }
+ // Check if the window size has changed
+ data.CSS.Adapter.Render(rd)
@@ -341,0 +354 @@ func open(data *Window) {
+ }
@@ -345 +358 @@ func open(data *Window) {
-func getRenderData(data *Window, shelf *library.Shelf, monitor *events.Monitor) {
+func getRenderData(data *Window, shelf *library.Shelf, monitor *events.Monitor) []element.State {
@@ -354 +367 @@ func getRenderData(data *Window, shelf *library.Shelf, monitor *events.Monitor)
- newDoc := copyDocument(data.CSS, data.document.Children[0], &data.document)
+ newDoc := AddStyles(data.CSS, data.document.Children[0], &data.document)
@@ -358 +371 @@ func getRenderData(data *Window, shelf *library.Shelf, monitor *events.Monitor)
- rd := data.render(newDoc, shelf)
+ rd := data.Render(newDoc, shelf)
@@ -362 +375 @@ func getRenderData(data *Window, shelf *library.Shelf, monitor *events.Monitor)
- addScroll(&data.document, data.CSS.State)
+ AddScroll(&data.document, data.CSS.State)
@@ -372 +385 @@ func getRenderData(data *Window, shelf *library.Shelf, monitor *events.Monitor)
- data.RenderData = rd
+ return rd
@@ -375 +388 @@ func getRenderData(data *Window, shelf *library.Shelf, monitor *events.Monitor)
-func copyDocument(c cstyle.CSS, node *element.Node, parent *element.Node) *element.Node {
+func AddStyles(c cstyle.CSS, node *element.Node, parent *element.Node) *element.Node {
@@ -389 +402 @@ func copyDocument(c cstyle.CSS, node *element.Node, parent *element.Node) *eleme
- n.Children[i] = copyDocument(c, node.Children[i], &n)
+ n.Children[i] = AddStyles(c, node.Children[i], &n)
@@ -397 +410 @@ func copyDocument(c cstyle.CSS, node *element.Node, parent *element.Node) *eleme
-func addScroll(n *element.Node, s map[string]element.State) {
+func AddScroll(n *element.Node, s map[string]element.State) {
@@ -402 +415 @@ func addScroll(n *element.Node, s map[string]element.State) {
- addScroll(n.Children[i], s)
+ AddScroll(n.Children[i], s)
@@ -406 +419 @@ func addScroll(n *element.Node, s map[string]element.State) {
-func createNode(node *html.Node, parent *element.Node, stylesheets *element.Styles) {
+func CreateNode(node *html.Node, parent *element.Node, stylesheets *element.Styles) {
@@ -447 +460 @@ func createNode(node *html.Node, parent *element.Node, stylesheets *element.Styl
- createNode(child, &newNode, stylesheets)
+ CreateNode(child, &newNode, stylesheets)
@@ -454 +467 @@ func createNode(node *html.Node, parent *element.Node, stylesheets *element.Styl
- createNode(child, parent, stylesheets)
+ CreateNode(child, parent, stylesheets)
package grim
import (
_ "embed"
"fmt"
adapter "grim/adapters"
"grim/canvas"
"grim/cstyle"
"grim/cstyle/plugins/crop"
"grim/cstyle/plugins/flex"
"grim/cstyle/plugins/inline"
"grim/cstyle/plugins/textAlign"
"grim/cstyle/transformers/banda"
img "grim/cstyle/transformers/image"
"time"
marginblock "grim/cstyle/transformers/margin-block"
"grim/cstyle/transformers/ol"
"grim/cstyle/transformers/scrollbar"
"grim/cstyle/transformers/text"
"grim/cstyle/transformers/ul"
"grim/font"
"grim/library"
"grim/scripts"
"grim/scripts/a"
"image"
"grim/element"
"grim/events"
"grim/utils"
"net/url"
"path/filepath"
"strconv"
"strings"
"github.com/golang/freetype/truetype"
"golang.org/x/net/html"
)
//go:embed master.css
var mastercss string
type Window struct {
CSS cstyle.CSS
document element.Node
Styles element.Styles
Scripts scripts.Scripts
RenderData []element.State
Rerender bool
shouldStop bool
}
func (w *Window) Document() *element.Node {
return &w.document
}
// !TODO: Add a Mux option to all a http server to map to the window
func (window *Window) HttpMux() {}
func (window *Window) Path(path string) {
styleSheets, styleTags, htmlNodes := parseHTMLFromFile(path, window.CSS.Adapter.FileSystem)
for _, v := range styleSheets {
data, _ := window.CSS.Adapter.FileSystem.ReadFile(v)
window.Styles.StyleTag(string(data))
}
for _, v := range styleTags {
window.Styles.StyleTag(v)
}
window.CSS.Path = filepath.Dir(path)
createNode(htmlNodes, &window.document, &window.Styles)
open(window)
}
func New(adapterFunction *adapter.Adapter, width, height int) Window {
w := Window{}
w.Styles = element.Styles{
PsuedoStyles: map[string]map[string]map[string]string{},
StyleMap: map[string][]*element.StyleMap{},
}
css := cstyle.CSS{
Width: float32(width),
Height: float32(height),
Adapter: adapterFunction,
}
w.Styles.StyleTag(mastercss)
// This is still apart of computestyle
css.AddPlugin(inline.Init())
css.AddPlugin(textAlign.Init())
css.AddPlugin(flex.Init())
css.AddPlugin(crop.Init())
css.AddTransformer(text.Init())
css.AddTransformer(banda.Init())
css.AddTransformer(scrollbar.Init())
css.AddTransformer(marginblock.Init())
css.AddTransformer(ul.Init())
css.AddTransformer(ol.Init())
css.AddTransformer(img.Init())
el := element.Node{}
document := el.CreateElement("ROOT")
document.Properties.Id = "ROOT"
document.StyleSheets = &w.Styles
s := scripts.Scripts{}
s.Add(a.Init())
w.CSS = css
w.Scripts = s
w.document = document
return w
}
func (w *Window) Open() {
for !w.shouldStop {
w.CSS.Adapter.Render(w.RenderData)
}
}
func (w *Window) render(doc *element.Node, shelf *library.Shelf) []element.State {
s := w.CSS.State
flatDoc := flatten(doc)
store := []element.State{}
keys := []string{}
for _, v := range flatDoc {
store = append(store, s[v.Properties.Id])
keys = append(keys, v.Properties.Id)
}
// Create a set of keys to keep
keysSet := make(map[string]struct{}, len(keys))
for _, key := range keys {
keysSet[key] = struct{}{}
}
// Iterate over the map and delete keys not in the set
for k := range s {
if _, found := keysSet[k]; !found {
textures := s[k].Textures
for _, t := range textures {
shelf.Delete(t)
}
delete(s, k)
}
}
for k, self := range store {
// Option: Have Grim render all elements
wbw := int(self.Width + self.Border.Left.Width + self.Border.Right.Width)
hbw := int(self.Height + self.Border.Top.Width + self.Border.Bottom.Width)
key := strconv.Itoa(wbw) + strconv.Itoa(hbw) + utils.RGBAtoString(self.Background)
exists := shelf.Check(key)
bounds := shelf.Bounds(key)
if exists && bounds[0] == int(wbw) && bounds[1] == int(hbw) {
lookup := make(map[string]struct{}, len(self.Textures))
for _, v := range self.Textures {
lookup[v] = struct{}{}
}
if _, found := lookup[key]; !found {
self.Textures = append([]string{key}, self.Textures...)
store[k] = self
}
} else if self.Background.A > 0 {
lookup := make(map[string]struct{}, len(self.Textures))
for _, v := range self.Textures {
lookup[v] = struct{}{}
}
if _, found := lookup[key]; !found {
// Only make the drawing if it's not found
can := canvas.NewCanvas(wbw, hbw)
can.BeginPath()
can.SetFillStyle(self.Background.R, self.Background.G, self.Background.B, self.Background.A)
can.SetLineWidth(10)
can.RoundedRect(0, 0, float64(wbw), float64(hbw),
[]float64{float64(self.Border.Radius.TopLeft), float64(self.Border.Radius.TopRight), float64(self.Border.Radius.BottomRight), float64(self.Border.Radius.BottomLeft)})
can.Fill()
can.ClosePath()
shelf.Set(key, *can.RGBA)
self.Textures = append([]string{key}, self.Textures...)
store[k] = self
}
}
}
return store
}
func flatten(n *element.Node) []*element.Node {
var nodes []*element.Node
nodes = append(nodes, n)
children := n.Children
if len(children) > 0 {
for _, ch := range children {
chNodes := flatten(ch)
nodes = append(nodes, chNodes...)
}
}
return nodes
}
func open(data *Window) {
shelf := library.Shelf{
Textures: map[string]image.RGBA{},
References: map[string]bool{},
}
data.document.ComputedStyle["width"] = strconv.Itoa(int(data.CSS.Width)) + "px"
data.document.ComputedStyle["height"] = strconv.Itoa(int(data.CSS.Height)) + "px"
data.CSS.Adapter.Library = &shelf
data.CSS.Adapter.Init(int(data.CSS.Width), int(data.CSS.Height))
data.CSS.State = map[string]element.State{}
data.CSS.State["ROOT"] = element.State{
Width: float32(data.CSS.Width),
Height: float32(data.CSS.Height),
}
// Load init font
if data.CSS.Fonts == nil {
data.CSS.Fonts = map[string]*truetype.Font{}
}
fid := "Georgia 16px false false"
if data.CSS.Fonts[fid] == nil {
f, _ := font.LoadFont("Georgia", 16, "", false, &data.CSS.Adapter.FileSystem)
data.CSS.Fonts[fid] = f
}
monitor := events.Monitor{
EventMap: make(map[string]element.Event),
Adapter: data.CSS.Adapter,
CSS: &data.CSS,
Focus: events.Focus{
Nodes: []string{},
Selected: -1,
SoftFocused: "",
LastClickWasFocused: false,
},
}
data.CSS.Adapter.AddEventListener("windowresize", func(e element.Event) {
wh := e.Data.(map[string]int)
data.CSS.Width = float32(wh["width"])
data.CSS.Height = float32(wh["height"])
data.document.ComputedStyle["width"] = strconv.Itoa(wh["width"]) + "px"
data.document.ComputedStyle["height"] = strconv.Itoa(wh["height"]) + "px"
getRenderData(data, &shelf, &monitor)
})
data.CSS.Adapter.AddEventListener("close", func(e element.Event) {
data.shouldStop = true
})
currentEvent := events.EventData{}
data.CSS.Adapter.AddEventListener("keydown", func(e element.Event) {
currentEvent.Key = e.Data.(int)
currentEvent.KeyState = true
currentEvent.Modifiers = events.Modifiers{
CtrlKey: e.CtrlKey,
ShiftKey: e.ShiftKey,
MetaKey: e.MetaKey,
AltKey: e.AltKey,
}
monitor.GetEvents(¤tEvent)
getRenderData(data, &shelf, &monitor)
})
data.CSS.Adapter.AddEventListener("keyup", func(e element.Event) {
currentEvent.Key = 0
currentEvent.KeyState = false
currentEvent.Modifiers = events.Modifiers{
CtrlKey: e.CtrlKey,
ShiftKey: e.ShiftKey,
MetaKey: e.MetaKey,
AltKey: e.AltKey,
}
monitor.GetEvents(¤tEvent)
getRenderData(data, &shelf, &monitor)
})
data.CSS.Adapter.AddEventListener("mousemove", func(e element.Event) {
pos := e.Data.([]int)
if pos[0] > 0 && pos[1] > 0 {
if pos[0] < int(data.CSS.Width) && pos[1] < int(data.CSS.Height) {
currentEvent.Position = pos
monitor.GetEvents(¤tEvent)
getRenderData(data, &shelf, &monitor)
}
}
})
data.CSS.Adapter.AddEventListener("scroll", func(e element.Event) {
currentEvent.ScrollY = e.Data.(int)
monitor.GetEvents(¤tEvent)
currentEvent.ScrollY = 0
getRenderData(data, &shelf, &monitor)
})
data.CSS.Adapter.AddEventListener("mousedown", func(e element.Event) {
currentEvent.Click = true
monitor.GetEvents(¤tEvent)
getRenderData(data, &shelf, &monitor)
})
data.CSS.Adapter.AddEventListener("mouseup", func(e element.Event) {
currentEvent.Click = false
monitor.GetEvents(¤tEvent)
getRenderData(data, &shelf, &monitor)
})
data.CSS.Adapter.AddEventListener("contextmenudown", func(e element.Event) {
currentEvent.Context = true
monitor.GetEvents(¤tEvent)
getRenderData(data, &shelf, &monitor)
})
data.CSS.Adapter.AddEventListener("contextmenuup", func(e element.Event) {
currentEvent.Context = true
monitor.GetEvents(¤tEvent)
getRenderData(data, &shelf, &monitor)
})
getRenderData(data, &shelf, &monitor)
}
// !TODO: This need to be better implemented but rn just testing
func getRenderData(data *Window, shelf *library.Shelf, monitor *events.Monitor) {
data.CSS.State["ROOT"] = element.State{
Width: float32(data.CSS.Width),
Height: float32(data.CSS.Height),
}
fmt.Println("_______________________")
monitor.RunEvents(data.document.Children[0])
start := time.Now()
newDoc := copyDocument(data.CSS, data.document.Children[0], &data.document)
data.CSS.ComputeNodeStyle(newDoc)
rd := data.render(newDoc, shelf)
data.CSS.Adapter.Load(rd)
addScroll(&data.document, data.CSS.State)
data.Scripts.Run(&data.document)
shelf.Clean()
// fmt.Println(newDoc.OuterHTML())
// !TODO: Should return effected node, then render those specific
// + I think have node.ComputeNodeStyle would make this nice
fmt.Println(time.Since(start))
data.RenderData = rd
}
func copyDocument(c cstyle.CSS, node *element.Node, parent *element.Node) *element.Node {
n := *node
n.Parent = parent
// !DEVMAN: Copying is done here, would like to remove this and add it to ComputeNodeStyle, so I can save a tree climb
// + Maybe just have this copy the node and the styles don't need to be recomputed everytime
if len(node.Children) > 0 {
n.Children = make([]*element.Node, 0, len(node.Children))
// n.Children = append(n.Children, node.Children...)
// for _, v := range node.Children {
for i := range node.Children {
n.Children = append(n.Children, node.Children[i])
}
for i := range node.Children {
n.Children[i] = copyDocument(c, node.Children[i], &n)
}
}
return &n
}
func addScroll(n *element.Node, s map[string]element.State) {
// !NOTE: This is the only spot you can pierce the vale
n.ScrollHeight = s[n.Properties.Id].ScrollHeight
n.ScrollWidth = s[n.Properties.Id].ScrollWidth
for i := range n.Children {
addScroll(n.Children[i], s)
}
}
func createNode(node *html.Node, parent *element.Node, stylesheets *element.Styles) {
if node.Type == html.ElementNode {
newNode := parent.CreateElement(node.Data)
newNode.Parent = parent
newNode.Properties.Id = element.GenerateUniqueId(parent, node.Data)
for _, attr := range node.Attr {
switch attr.Key {
case "class":
classes := strings.Split(attr.Val, " ")
for _, class := range classes {
newNode.ClassList.Add(class)
}
case "id":
newNode.Id = attr.Val
case "contenteditable":
if attr.Val == "" || attr.Val == "true" {
newNode.ContentEditable = true
}
case "href":
newNode.Href = attr.Val
case "src":
newNode.Src = attr.Val
case "title":
newNode.Title = attr.Val
case "tabindex":
newNode.TabIndex, _ = strconv.Atoi(attr.Val)
case "disabled":
newNode.Disabled = true
case "required":
newNode.Required = true
case "checked":
newNode.Checked = true
default:
newNode.SetAttribute(attr.Key, attr.Val)
}
}
newNode.InnerText = strings.TrimSpace(utils.GetInnerText(node))
parent.StyleSheets.GetStyles(&newNode)
// Recursively traverse child nodes
for child := node.FirstChild; child != nil; child = child.NextSibling {
if child.Type == html.ElementNode {
createNode(child, &newNode, stylesheets)
}
}
parent.AppendChild(&newNode)
} else {
for child := node.FirstChild; child != nil; child = child.NextSibling {
if child.Type == html.ElementNode {
createNode(child, parent, stylesheets)
}
}
}
}
func parseHTMLFromFile(path string, fs adapter.FileSystem) ([]string, []string, *html.Node) {
file, _ := fs.ReadFile(path)
doc, _ := html.Parse(strings.NewReader(string(file)))
wrapAllTextNodes(doc)
unwrapSingleTextChildren(doc)
// Extract stylesheet link tags and style tags
stylesheets := extractStylesheets(doc, filepath.Dir(path))
styleTags := extractStyleTags(doc)
return stylesheets, styleTags, doc
}
func extractStylesheets(n *html.Node, baseDir string) []string {
var stylesheets []string
var dfs func(*html.Node)
dfs = func(node *html.Node) {
if node.Type == html.ElementNode && node.Data == "link" {
var href string
isStylesheet := false
for _, attr := range node.Attr {
if attr.Key == "rel" && attr.Val == "stylesheet" {
isStylesheet = true
} else if attr.Key == "href" {
href = attr.Val
}
}
if isStylesheet {
resolvedHref := localizePath(baseDir, href)
stylesheets = append(stylesheets, resolvedHref)
}
}
for c := node.FirstChild; c != nil; c = c.NextSibling {
dfs(c)
}
}
dfs(n)
return stylesheets
}
func extractStyleTags(n *html.Node) []string {
var styleTags []string
var dfs func(*html.Node)
dfs = func(node *html.Node) {
if node.Type == html.ElementNode && node.Data == "style" {
var styleContent strings.Builder
for c := node.FirstChild; c != nil; c = c.NextSibling {
if c.Type == html.TextNode {
styleContent.WriteString(c.Data)
}
}
styleTags = append(styleTags, styleContent.String())
}
for c := node.FirstChild; c != nil; c = c.NextSibling {
dfs(c)
}
}
dfs(n)
return styleTags
}
func localizePath(rootPath, filePath string) string {
// Check if the file path has a scheme, indicating it's a URL
u, err := url.Parse(filePath)
if err == nil && u.Scheme != "" {
return filePath
}
// Join the root path and the file path to create an absolute path
absPath := filepath.Join(rootPath, filePath)
// If the absolute path is the same as the original path, return it
if absPath == filePath {
return filePath
}
return "./" + absPath
}
// wrapAllTextNodes wraps all non-empty text nodes with elements
func wrapAllTextNodes(n *html.Node) {
// Skip script and style tags
if n.Type == html.ElementNode && (n.Data == "script" || n.Data == "style") {
return
}
// Process children (collect first to avoid traversal issues)
var children []*html.Node
for c := n.FirstChild; c != nil; c = c.NextSibling {
children = append(children, c)
}
for _, child := range children {
// Wrap text nodes
child.Data = strings.TrimSpace(child.Data)
child.Data = strings.ReplaceAll(child.Data, "\t", " ")
// Replace repeating spaces with a single space
for strings.Contains(child.Data, " ") {
child.Data = strings.ReplaceAll(child.Data, " ", " ")
}
if child.Type == html.TextNode && child.Data != "" {
textEl := &html.Node{
Type: html.ElementNode,
Data: "text",
}
n.InsertBefore(textEl, child)
n.RemoveChild(child)
textEl.AppendChild(child)
}
// Process child's children recursively
wrapAllTextNodes(child)
}
}
// unwrapSingleTextChildren removes elements that are the only child of their parent
func unwrapSingleTextChildren(n *html.Node) {
// Skip script and style tags
if n.Type == html.ElementNode && (n.Data == "script" || n.Data == "style") {
return
}
// Check if this element has exactly one child and it's a element
if n.Type == html.ElementNode && n.Data != "text" && n.FirstChild != nil &&
n.FirstChild.NextSibling == nil && n.FirstChild.Type == html.ElementNode &&
n.FirstChild.Data == "text" {
textEl := n.FirstChild
textContent := textEl.FirstChild
if textContent != nil {
// Move the text content directly under this element
textEl.RemoveChild(textContent)
n.InsertBefore(textContent, textEl)
n.RemoveChild(textEl)
}
}
// Process all children recursively
for c := n.FirstChild; c != nil; c = c.NextSibling {
unwrapSingleTextChildren(c)
}
}