diff --git a/Context.go b/Context.go index 736a756c..6c081aae 100644 --- a/Context.go +++ b/Context.go @@ -146,7 +146,7 @@ func GetState[T any, PT genericDisposable[T]](c *context, id string) PT { c.m.Unlock() data, isOk := s.data.(PT) - Assert(isOk, "Context", "GetState", fmt.Sprintf("got state of unexpected type: expected %T, instead found %T", new(T), s.data)) + Assert(isOk, "Context", "GetState", "got state of unexpected type: expected %T, instead found %T", new(T), s.data) return data } diff --git a/ExtraWidgets.go b/ExtraWidgets.go index 31fe9649..1cbe0b50 100644 --- a/ExtraWidgets.go +++ b/ExtraWidgets.go @@ -535,7 +535,6 @@ func (d *DatePickerWidget) Build() { var row []Widget for _, day := range week { - day := day // hack for golang ranges if day == 0 { row = append(row, Label(" ")) continue diff --git a/FontAtlasProsessor.go b/FontAtlasProsessor.go index 008d009f..60a7a272 100644 --- a/FontAtlasProsessor.go +++ b/FontAtlasProsessor.go @@ -290,7 +290,7 @@ func (a *FontAtlas) rebuildFontAtlas() { } else { fontConfig.SetFontDataOwnedByAtlas(false) fonts.AddFontFromMemoryTTFV( - uintptr(unsafe.Pointer(imgui.SliceToPtr(fontInfo.fontByte))), //nolint:gosec // we need this here + uintptr(unsafe.Pointer(imgui.SliceToPtr(fontInfo.fontByte))), int32(len(fontInfo.fontByte)), fontInfo.size, fontConfig, @@ -328,7 +328,7 @@ func (a *FontAtlas) rebuildFontAtlas() { fontConfig := imgui.NewFontConfig() fontConfig.SetFontDataOwnedByAtlas(false) f = fonts.AddFontFromMemoryTTFV( - uintptr(unsafe.Pointer(imgui.SliceToPtr(fontInfo.fontByte))), //nolint:gosec // we need this here + uintptr(unsafe.Pointer(imgui.SliceToPtr(fontInfo.fontByte))), int32(len(fontInfo.fontByte)), fontInfo.size, fontConfig, diff --git a/FontAtlasProsessor.go~ b/FontAtlasProsessor.go~ new file mode 100644 index 00000000..bfe6b520 --- /dev/null +++ b/FontAtlasProsessor.go~ @@ -0,0 +1,348 @@ +package giu + +import ( + "fmt" + "log" + "runtime" + "strings" + "sync" + "unsafe" + + "github.com/AllenDang/cimgui-go/imgui" + "github.com/AllenDang/go-findfont" +) + +const ( + preRegisterString = " \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" + windows = "windows" + defaultFontSize = 14 +) + +// FontInfo represents a giu implementation of imgui font. +type FontInfo struct { + fontName string + fontPath string + fontByte []byte + size float32 +} + +func (f *FontInfo) String() string { + return fmt.Sprintf("%s:%.2f", f.fontName, f.size) +} + +func (f *FontInfo) SetSize(size float32) *FontInfo { + result := *f + result.size = size + + for _, i := range Context.FontAtlas.extraFonts { + if i.String() == result.String() { + return &result + } + } + + Context.FontAtlas.extraFonts = append(Context.FontAtlas.extraFonts, result) + Context.FontAtlas.shouldRebuildFontAtlas = true + + return &result +} + +type FontAtlas struct { + shouldRebuildFontAtlas bool + stringMap sync.Map // key is rune, value indicates whether it's a new rune. + defaultFonts []FontInfo + extraFonts []FontInfo + extraFontMap map[string]*imgui.Font + fontSize float32 +} + +func newFontAtlas() *FontAtlas { + result := FontAtlas{ + extraFontMap: make(map[string]*imgui.Font), + fontSize: defaultFontSize, + } + + // Pre register numbers + result.RegisterString(preRegisterString) + + // Pre-register fonts + switch runtime.GOOS { + case "darwin": + // English font + result.registerDefaultFont("Menlo", result.fontSize) + // Chinese font + result.registerDefaultFont("STHeiti", result.fontSize-1) + // Jananese font + result.registerDefaultFont("ヒラギノ角ゴシック W0", result.fontSize+3) + // Korean font + result.registerDefaultFont("AppleSDGothicNeo", result.fontSize+2) + case windows: + // English font + result.registerDefaultFont("Calibri", result.fontSize+2) + // Chinese font + result.registerDefaultFont("MSYH", result.fontSize+2) + // Japanese font + result.registerDefaultFont("MSGOTHIC", result.fontSize+2) + // Korean font + result.registerDefaultFont("MALGUNSL", result.fontSize+2) + case "linux": + // English fonts + result.registerDefaultFonts([]FontInfo{ + { + fontName: "FreeSans.ttf", + size: result.fontSize + 1, + }, + { + fontName: "FiraCode-Medium", + size: result.fontSize + 1, + }, + { + fontName: "sans", + size: result.fontSize + 1, + }, + }) + // Chinese fonts + result.registerDefaultFonts([]FontInfo{ + { + fontName: "wqy-microhei", + size: result.fontSize + 1, + }, + { + fontName: "SourceHanSansCN", + size: result.fontSize + 3, + }, + }) + } + + return &result +} + +// SetDefaultFontSize sets the default font size. Invoke this before MasterWindow.NewMasterWindow(..). +func (a *FontAtlas) SetDefaultFontSize(size float32) { + a.fontSize = size +} + +// SetDefaultFont changes default font. +func (a *FontAtlas) SetDefaultFont(fontName string, size float32) { + fontPath, err := findfont.Find(fontName) + if err != nil { + log.Fatalf("Cannot find font %s", fontName) + return + } + + fontInfo := FontInfo{fontName: fontName, fontPath: fontPath, size: size} + a.defaultFonts = append([]FontInfo{fontInfo}, a.defaultFonts...) +} + +// SetDefaultFontFromBytes changes default font by bytes of the font file. +func (a *FontAtlas) SetDefaultFontFromBytes(fontBytes []byte, size float32) { + a.defaultFonts = append([]FontInfo{ + { + fontByte: fontBytes, + size: size, + }, + }, a.defaultFonts...) +} + +func (a *FontAtlas) GetDefaultFonts() []FontInfo { + return a.defaultFonts +} + +// AddFont adds font by name, if the font is found, return *FontInfo, otherwise return nil. +// To use added font, use giu.Style().SetFont(...). +func (a *FontAtlas) AddFont(fontName string, size float32) *FontInfo { + fontPath, err := findfont.Find(fontName) + if err != nil { + fmt.Printf("[Warning]Cannot find font %s at system, related text will not be rendered.\n", fontName) + return nil + } + + fi := FontInfo{ + fontName: fontName, + fontPath: fontPath, + size: size, + } + + a.extraFonts = append(a.extraFonts, fi) + + return &fi +} + +// AddFontFromBytes does similar to AddFont, but using data from memory. +func (a *FontAtlas) AddFontFromBytes(fontName string, fontBytes []byte, size float32) *FontInfo { + fi := FontInfo{ + fontName: fontName, + fontByte: fontBytes, + size: size, + } + + a.extraFonts = append(a.extraFonts, fi) + + return &fi +} + +func (a *FontAtlas) registerDefaultFont(fontName string, size float32) { + fontPath, err := findfont.Find(fontName) + if err != nil { + return + } + + fontInfo := FontInfo{fontName: fontName, fontPath: fontPath, size: size} + a.defaultFonts = append(a.defaultFonts, fontInfo) +} + +func (a *FontAtlas) registerDefaultFonts(fontInfos []FontInfo) { + var firstFoundFont *FontInfo + + for _, fi := range fontInfos { + fontPath, err := findfont.Find(fi.fontName) + if err == nil { + firstFoundFont = &FontInfo{fontName: fi.fontName, fontPath: fontPath, size: fi.size} + break + } + } + + if firstFoundFont != nil { + a.defaultFonts = append(a.defaultFonts, *firstFoundFont) + } +} + +// RegisterString register string to font atlas builder. +// Note only register strings that will be displayed on the UI. +func (a *FontAtlas) RegisterString(str string) string { + for _, s := range str { + if _, ok := a.stringMap.Load(s); !ok { + a.stringMap.Store(s, false) + a.shouldRebuildFontAtlas = true + } + } + + return str +} + +// RegisterStringPointer registers string pointer to font atlas builder. +// Note only register strings that will be displayed on the UI. +func (a *FontAtlas) RegisterStringPointer(str *string) *string { + a.RegisterString(*str) + return str +} + +// RegisterStringSlice calls RegisterString for each slice element. +func (a *FontAtlas) RegisterStringSlice(str []string) []string { + for _, s := range str { + a.RegisterString(s) + } + + return str +} + +// Rebuild font atlas when necessary. +func (a *FontAtlas) rebuildFontAtlas() { + if !a.shouldRebuildFontAtlas { + return + } + + fonts := Context.IO().Fonts() + fonts.Clear() + + var sb strings.Builder + + a.stringMap.Range(func(k, v any) bool { + a.stringMap.Store(k, true) + + if ks, ok := k.(rune); ok { + sb.WriteRune(ks) + } + + return true + }) + + ranges := imgui.NewGlyphRange() + builder := imgui.NewFontGlyphRangesBuilder() + + // Because we pre-registered numbers, so default string map's length should greater then 11. + if sb.Len() > len(preRegisterString) { + builder.AddText(sb.String()) + } else { + builder.AddRanges(fonts.GlyphRangesDefault()) + } + + builder.BuildRanges(ranges) + + if len(a.defaultFonts) > 0 { + fontConfig := imgui.NewFontConfig() + fontConfig.SetOversampleH(2) + fontConfig.SetOversampleV(2) + fontConfig.SetRasterizerMultiply(1.5) + + for i, fontInfo := range a.defaultFonts { + if i > 0 { + fontConfig.SetMergeMode(true) + } + + // Scale font size with DPI scale factor + if runtime.GOOS == windows { + xScale, _ := Context.backend.ContentScale() + fontInfo.size *= xScale + } + + if len(fontInfo.fontByte) == 0 { + fonts.AddFontFromFileTTFV(fontInfo.fontPath, fontInfo.size, fontConfig, ranges.Data()) + } else { + fontConfig.SetFontDataOwnedByAtlas(false) + fonts.AddFontFromMemoryTTFV( + uintptr(unsafe.Pointer(imgui.SliceToPtr(fontInfo.fontByte))), //nolint:gosec // we need this here + int32(len(fontInfo.fontByte)), // nolint:gosec // no risk heree I think, its cimgui-go API + fontInfo.size, + fontConfig, + ranges.Data(), + ) + } + } + + // Fall back if no font is added + if fonts.FontCount() == 0 { + fonts.AddFontDefault() + } + } else { + fonts.AddFontDefault() + } + + // Add extra fonts + for _, fontInfo := range a.extraFonts { + // Scale font size with DPI scale factor + if runtime.GOOS == windows { + xScale, _ := Context.backend.ContentScale() + fontInfo.size *= xScale + } + + // Store imgui.Font for PushFont + var f *imgui.Font + if len(fontInfo.fontByte) == 0 { + f = fonts.AddFontFromFileTTFV( + fontInfo.fontPath, + fontInfo.size, + imgui.NewFontConfig(), + ranges.Data(), + ) + } else { + fontConfig := imgui.NewFontConfig() + fontConfig.SetFontDataOwnedByAtlas(false) + f = fonts.AddFontFromMemoryTTFV( + uintptr(unsafe.Pointer(imgui.SliceToPtr(fontInfo.fontByte))), //nolint:gosec // we need this here + int32(len(fontInfo.fontByte)), // nolint:gosec // no risk heree I think, its cimgui-go API + fontInfo.size, + fontConfig, + ranges.Data(), + ) + } + + a.extraFontMap[fontInfo.String()] = f + } + + fontTextureImg, w, h, _ := fonts.GetTextureDataAsRGBA32() + tex := Context.backend.CreateTexture(fontTextureImg, int(w), int(h)) + fonts.SetTexID(tex) + fonts.SetTexReady(true) + + a.shouldRebuildFontAtlas = false +} diff --git a/MasterWindow.go b/MasterWindow.go index 10670f2b..afa7e363 100644 --- a/MasterWindow.go +++ b/MasterWindow.go @@ -81,6 +81,7 @@ type MasterWindow struct { // see examples/helloworld/. func NewMasterWindow(title string, width, height int, flags MasterWindowFlags) *MasterWindow { imGuiContext := imgui.CreateContext() + implot.PlotCreateContext() imnodes.ImNodesCreateContext() diff --git a/Widgets.go b/Widgets.go index a61c316e..15cde5f0 100644 --- a/Widgets.go +++ b/Widgets.go @@ -206,7 +206,6 @@ func (c *ComboWidget) Build() { if imgui.BeginComboV(Context.FontAtlas.RegisterString(c.label.String()), c.previewValue, imgui.ComboFlags(c.flags)) { for i, item := range c.items { - i := i if imgui.SelectableBool(fmt.Sprintf("%s##%d", item, i)) { *c.selected = int32(i) if c.onChange != nil {