Skip to content

Commit

Permalink
Merge pull request #705 from gucio321/add-markdown
Browse files Browse the repository at this point in the history
Add markdown widget
  • Loading branch information
gucio321 authored Nov 12, 2024
2 parents 96a0cec + ccdcb43 commit b650f94
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 142 deletions.
251 changes: 155 additions & 96 deletions Markdown.go
Original file line number Diff line number Diff line change
@@ -1,127 +1,186 @@
//nolint:gocritic,govet,wsl,revive // this file is TODO. We don't want commentedOutCode lint issues here.
package giu

import (
"image"
"image/color"
"net/http"
"strings"
"time"

"github.com/AllenDang/cimgui-go/backend"
"github.com/AllenDang/cimgui-go/imgui"
"github.com/AllenDang/cimgui-go/immarkdown"
)

type markdownState struct {
cfg immarkdown.MarkdownConfig
images map[string]immarkdown.MarkdownImageData
}

func (m *markdownState) Dispose() {
// noop
}

// MarkdownWidget implements DearImGui markdown extension
// https://github.com/juliettef/imgui_markdown
// It is like LabelWidget but with md formatting.
// TODO: re-implement this.
type MarkdownWidget struct {
md *string
linkCb func(url string)
// headers []imgui.MarkdownHeaderData
md string
id ID
headers [3]immarkdown.MarkdownHeadingFormat
}

// Markdown creates new markdown widget.
func Markdown(md *string) *MarkdownWidget {
panic("MarkdownWidget is not implemented yet!")
func (m *MarkdownWidget) getState() *markdownState {
if s := GetState[markdownState](Context, m.id); s != nil {
return s
}

newState := m.newState()
SetState[markdownState](Context, m.id, newState)

return &MarkdownWidget{
md: md,
linkCb: OpenURL,
return newState
}

func (m *MarkdownWidget) newState() *markdownState {
cfg := immarkdown.NewEmptyMarkdownConfig()
fmtCb := immarkdown.MarkdownFormalCallback(func(data *immarkdown.MarkdownFormatInfo, start bool) {
immarkdown.DefaultMarkdownFormatCallback(*data, start)
})

cfg.SetFormatCallback(&fmtCb)

imgCb := immarkdown.MarkdownImageCallback(func(data immarkdown.MarkdownLinkCallbackData) immarkdown.MarkdownImageData {
link := data.Link()[:data.LinkLength()] // this is because imgui_markdown returns the whole text starting on link and returns link length (for some reason)
if existing, ok := m.getState().images[link]; ok {
return existing
}

result := mdLoadImage(link)
m.getState().images[link] = result

return result
})

cfg.SetImageCallback(&imgCb)

return &markdownState{
cfg: *cfg,
images: make(map[string]immarkdown.MarkdownImageData),
}
}

// Markdown creates new markdown widget.
func Markdown(md string) *MarkdownWidget {
return (&MarkdownWidget{
md: md,
id: GenAutoID("MarkdownWidget"),
headers: [3]immarkdown.MarkdownHeadingFormat{
*immarkdown.NewEmptyMarkdownHeadingFormat(),
*immarkdown.NewEmptyMarkdownHeadingFormat(),
*immarkdown.NewEmptyMarkdownHeadingFormat(),
},
}).OnLink(OpenURL)
}

// OnLink sets another than default link callback.
// NOTE: due to cimgui-go's limitation https://github.com/AllenDang/cimgui-go?tab=readme-ov-file#callbacks
// we clear MarkdownLinkCallback pool every frame. No further action from you should be required (just feel informed).
// ref (*MasterWindow).beforeRender.
func (m *MarkdownWidget) OnLink(cb func(url string)) *MarkdownWidget {
m.linkCb = cb
igCb := immarkdown.MarkdownLinkCallback(func(data immarkdown.MarkdownLinkCallbackData) {
link := data.Link()[:data.LinkLength()]
cb(link)
})

m.getState().cfg.SetLinkCallback(&igCb)

return m
}

// Header sets header formatting
// NOTE: level (counting from 0!) is header level. (for instance, header `# H1` will have level 0).
// NOTE: since cimgui-go there are only 3 levels (so level < 3 here). This will panic if level >= 3!
// TODO: it actually doesn't work.
func (m *MarkdownWidget) Header(level int, font *FontInfo, separator bool) *MarkdownWidget {
// ensure if header data are at least as long as level
// if m.headers == nil {
// m.headers = make([]imgui.MarkdownHeaderData, level)
//}
// ensure level is in range
Assert(level < 3, "MarkdownWidget", "Header", "Header level must be less than 3!")

// if level <= len(m.headers) {
// m.headers = append(m.headers, make([]imgui.MarkdownHeaderData, len(m.headers)-level+1)...)
//}
m.headers[level] = *immarkdown.NewEmptyMarkdownHeadingFormat()

// if font != nil {
// if f, ok := Context.FontAtlas.extraFontMap[font.String()]; ok {
// m.headers[level].Font = *f
// }
//}
if font != nil {
if f, ok := Context.FontAtlas.extraFontMap[font.String()]; ok {
m.headers[level].SetFont(f)
}
}

// m.headers[level].HasSeparator = separator
m.headers[level].SetSeparator(separator)

state := m.getState()
state.cfg.SetHeadingFormats(&m.headers)

return m
}

// Build implements Widget interface.
func (m *MarkdownWidget) Build() {
// imgui.Markdown(Context.FontAtlas.RegisterStringPointer(m.md), m.linkCb, loadImage, m.headers)
state := m.getState()
immarkdown.Markdown(
Context.FontAtlas.RegisterString(m.md),
uint64(len(m.md)),
state.cfg,
)
}

//func loadImage(path string) imgui.MarkdownImageData {
// var img *image.RGBA
//
// var err error
//
// switch {
// case strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://"):
// Load image from url
//client := &http.Client{Timeout: 5 * time.Second}
//resp, respErr := client.Get(path)
//
//if respErr != nil {
// return imgui.MarkdownImageData{}
//}
//
//defer func() {
// closeErr := resp.Body.Close()
// Assert((closeErr == nil), "MarkdownWidget", "loadImage", "Could not close http request!")
//}()
//
//rgba, _, imgErr := image.Decode(resp.Body)
//if imgErr != nil {
// return imgui.MarkdownImageData{}
//}
//
//img = ImageToRgba(rgba)
//default:
// img, err = LoadImage(path)
// if err != nil {
// return imgui.MarkdownImageData{}
// }
//}
//
//size := img.Bounds()
//
//nolint:gocritic // TODO/BUG: figure out, why it doesn't work as expected and consider
//if current workaround is save
///*
// tex := &Texture{}
// NewTextureFromRgba(img, func(t *Texture) {
// fmt.Println("creating texture")
// tex.id = t.id
// })
//*/
//
//var id imgui.TextureID
//
//mainthread.Call(func() {
// var err error
// id, err = Context.renderer.LoadImage(img)
// if err != nil {
// return
// }
//})
//
//return imgui.MarkdownImageData{
// TextureID: &id,
// Scale: true,
// Size: imgui.Vec2{
// X: float32(size.Dx()),
// Y: float32(size.Dy()),
// },
// UseLinkCallback: true,
// default values
//Uv0: ToVec2(image.Point{0, 0}),
//Uv1: ToVec2(image.Point{1, 1}),
//TintColor: ToVec4Color(color.RGBA{255, 255, 255, 255}),
//BorderColor: ToVec4Color(color.RGBA{0, 0, 0, 0}),
//}
//}
func mdLoadImage(path string) immarkdown.MarkdownImageData {
var (
img *image.RGBA
err error
)

switch {
case strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://"):
// Load image from url
client := &http.Client{Timeout: 5 * time.Second}

resp, respErr := client.Get(path)
if respErr != nil {
return *immarkdown.NewEmptyMarkdownImageData()
}

defer func() {
closeErr := resp.Body.Close()
Assert((closeErr == nil), "MarkdownWidget", "mdLoadImage", "Could not close http request!")
}()

rgba, _, imgErr := image.Decode(resp.Body)
if imgErr != nil {
return *immarkdown.NewEmptyMarkdownImageData()
}

img = ImageToRgba(rgba)
default:
img, err = LoadImage(path)
if err != nil {
return *immarkdown.NewEmptyMarkdownImageData()
}
}

size := img.Bounds()
id := backend.NewTextureFromRgba(img).ID

result := immarkdown.NewEmptyMarkdownImageData()
result.SetUsertextureid(id)
result.SetSize(imgui.Vec2{
X: float32(size.Dx()),
Y: float32(size.Dy()),
})
result.SetUseLinkCallback(true)
result.SetUv0(ToVec2(image.Point{0, 0}))
result.SetUv1(ToVec2(image.Point{1, 1}))
result.SetTintcol(ToVec4Color(color.RGBA{255, 255, 255, 255}))
result.SetBordercol(ToVec4Color(color.RGBA{0, 0, 0, 0}))

result.SetIsValid(true)

return *result
}
5 changes: 5 additions & 0 deletions MasterWindow.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/AllenDang/cimgui-go/backend/glfwbackend"
"github.com/AllenDang/cimgui-go/imgui"
"github.com/AllenDang/cimgui-go/imguizmo"
"github.com/AllenDang/cimgui-go/immarkdown"
"github.com/AllenDang/cimgui-go/imnodes"
"github.com/AllenDang/cimgui-go/implot"
"golang.org/x/image/colornames"
Expand Down Expand Up @@ -201,6 +202,10 @@ func (w *MasterWindow) sizeChange(_, _ int) {
}

func (w *MasterWindow) beforeRender() {
// Clean callbacks
// see https://github.com/AllenDang/cimgui-go?tab=readme-ov-file#callbacks
immarkdown.ClearMarkdownLinkCallbackPool()

Context.FontAtlas.rebuildFontAtlas()

// process texture load requests
Expand Down
Loading

0 comments on commit b650f94

Please sign in to comment.