package main import ( "math" "strconv" "time" "codehub.onpointcoding.net/sean/go-susapp/enum" "codehub.onpointcoding.net/sean/go-susapp/innernetobjects" "codehub.onpointcoding.net/sean/go-susapp/util" "github.com/gotk3/gotk3/cairo" "github.com/gotk3/gotk3/gtk" "github.com/gotk3/gotk3/pango" ) type Renderer struct { state *State da *gtk.DrawingArea cr *cairo.Context w float64 h float64 mouseX float64 mouseY float64 delta int64 } var defaultRenderFont string = "Noto Sans" func NewRenderer(state *State) *Renderer { return &Renderer{state: state} } func (r *Renderer) Draw(da *gtk.DrawingArea, cr *cairo.Context) { r.delta = time.Now().UnixMilli() r.da = da r.cr = cr allocation := da.GetAllocation() r.w = float64(allocation.GetWidth()) r.h = float64(allocation.GetHeight()) versionText := GetVersionFormatted(r.state.Version) // Background r.DrawFilledRectangle(0, 0, r.w, r.h, 0x14181c) // Version numbers r.DrawText(10, 10, versionText, "Noto Sans Display", 16, 0xffffff) if r.state.Screen == enum.SCREEN_TITLE { colorLen := enum.GetPlayerTotalColors() for i := 0; i < colorLen; i++ { r.DrawFilledRectangle(float64(i)*20+(r.w-float64(colorLen)-float64(colorLen*20))/2, 0, 20, 20, enum.GetPlayerMainColor(i)) r.DrawFilledRectangle(float64(i)*20+(r.w-float64(colorLen)-float64(colorLen*20))/2, 20, 20, 20, enum.GetPlayerShadedColor(i)) } r.DrawButton(r.w-120, 20, 100, 40, r.state.Settings.PlayerName, defaultRenderFont, 16) r.DrawButton(r.w-120, 60, 100, 40, r.state.Settings.ServerAddress, defaultRenderFont, 16) r.DrawButton(r.w-120, 100, 100, 40, strconv.FormatInt(r.state.Settings.ServerPort, 10), defaultRenderFont, 16) r.DrawTextInput((r.w-200)/2, (r.h-20)/2-60, 200, 40, r.state.TypedGameID, defaultRenderFont, 16, r.state.IsTypingReady) r.DrawButton((r.w-200)/2, (r.h-20)/2, 200, 40, "Play", defaultRenderFont, 16) r.DrawButton(r.w-60, r.h-60, 40, 40, "⚙", defaultRenderFont, 16) } else if r.state.Screen == enum.SCREEN_WAITING { mw := r.w / 2 mh := r.h / 2 a := float64(r.delta/10) * (math.Pi / 180) b := a + math.Pi/2 c := a + math.Pi d := a + math.Pi*3/2 r.DrawFilledRoundedRectangle(mw+50*math.Sin(a)-20, mh+50*math.Cos(a)-20, 40, 40, 1, 20, 0xffffff) r.DrawFilledRoundedRectangle(mw+50*math.Sin(b)-20, mh+50*math.Cos(b)-20, 40, 40, 1, 20, 0xffffff) r.DrawFilledRoundedRectangle(mw+50*math.Sin(c)-20, mh+50*math.Cos(c)-20, 40, 40, 1, 20, 0xffffff) r.DrawFilledRoundedRectangle(mw+50*math.Sin(d)-20, mh+50*math.Cos(d)-20, 40, 40, 1, 20, 0xffffff) } else if r.state.Screen == enum.SCREEN_LOBBY { r.DrawButton((r.w-100)/2, r.h-60, 100, 40, util.CodeFromGameID(r.state.CurrentGame.GameID), defaultRenderFont, 16) r.DrawButton(r.w-60, 20, 40, 40, "⚙", defaultRenderFont, 16) if r.state.CurrentGame.GamePrivacy == 1 { r.DrawButton((r.w-100)/2+120, r.h-60, 100, 40, "Public", defaultRenderFont, 16) } else if r.state.CurrentGame.GamePrivacy == 0 { r.DrawButton((r.w-100)/2+120, r.h-60, 100, 40, "Private", defaultRenderFont, 16) } r.DrawPlayers() if r.state.IsLobbyMenuOpen { mw := r.w / 2 mh := r.h / 2 overlayBottom := mh + 200 r.DrawFilledRectangle(0, 0, r.w, r.h, 0x80000000) r.DrawFilledRoundedRectangle(mw-200, mh-200, 400, 400, 1, 20, 0x000000) r.DrawButton((r.w-200)/2, overlayBottom-60, 200, 40, "Return to Game", defaultRenderFont, 16) r.DrawButton((r.w-200)/2, overlayBottom-120, 200, 40, "Leave Game", defaultRenderFont, 16) } } else if r.state.Screen == enum.SCREEN_ERROR { mw := r.w / 2 mh := r.h / 2 r.DrawFilledRoundedRectangle(mw-200, mh-200, 400, 400, 1, 20, 0x000000) r.DrawText(mw-190, mh-190, "Disconnected", defaultRenderFont, 16, 0xffffff) r.DrawButton(mw+170, mh-210, 40, 40, "×", defaultRenderFont, 16) if r.state.DisconnectReason == 0xfe { r.DrawWrappedText(mw-190, mh-156, 380, r.state.DisconnectStringReason, defaultRenderFont, 16, 0xffffff) } else { r.DrawWrappedText(mw-190, mh-156, 380, enum.DisconnectMessageText(r.state.DisconnectReason), defaultRenderFont, 16, 0xffffff) } } } func (r *Renderer) SetRGB(red int, green int, blue int, alpha int) { r.cr.SetSourceRGBA(float64(red)/0xff, float64(green)/0xff, float64(blue)/0xff, 0xff/float64(alpha)) } func (r *Renderer) SetHex(hex int) { r.SetRGB((hex/(256*256))%256, (hex/256)%256, hex%256, (hex/(256*256*256))%256) } func (r *Renderer) DrawRectangle(x float64, y float64, w float64, h float64, color int) { r.SetHex(color) r.cr.Rectangle(x, y, w, h) r.cr.Stroke() } func (r *Renderer) DrawFilledRectangle(x float64, y float64, w float64, h float64, color int) { r.SetHex(color) r.cr.Rectangle(x, y, w, h) r.cr.Fill() } func (r *Renderer) DrawBorderedRectangle(x float64, y float64, w float64, h float64, color int, border int) { r.DrawFilledRectangle(x, y, w, h, color) r.DrawRectangle(x, y, w, h, border) } func (r *Renderer) DrawRoundedRectangle(x float64, y float64, w float64, h float64, aspect float64, corner_radius float64, color int) { a := corner_radius / aspect d := math.Pi / 180 r.cr.NewPath() r.cr.Arc(x+w-a, y+a, a, -90*d, 0*d) r.cr.Arc(x+w-a, y+h-a, a, 0*d, 90*d) r.cr.Arc(x+a, y+h-a, a, 90*d, 180*d) r.cr.Arc(x+a, y+a, a, 180*d, 270*d) r.cr.ClosePath() r.SetHex(color) r.cr.Stroke() } func (r *Renderer) DrawFilledRoundedRectangle(x float64, y float64, w float64, h float64, aspect float64, corner_radius float64, color int) { a := corner_radius / aspect d := math.Pi / 180 r.cr.NewPath() r.cr.Arc(x+w-a, y+a, a, -90*d, 0*d) r.cr.Arc(x+w-a, y+h-a, a, 0*d, 90*d) r.cr.Arc(x+a, y+h-a, a, 90*d, 180*d) r.cr.Arc(x+a, y+a, a, 180*d, 270*d) r.cr.ClosePath() r.SetHex(color) r.cr.FillPreserve() } func (r *Renderer) DrawBorderedRoundedRectangle(x float64, y float64, w float64, h float64, aspect float64, corner_radius float64, color int, border int) { a := corner_radius / aspect d := math.Pi / 180 r.cr.NewPath() r.cr.Arc(x+w-a, y+a, a, -90*d, 0*d) r.cr.Arc(x+w-a, y+h-a, a, 0*d, 90*d) r.cr.Arc(x+a, y+h-a, a, 90*d, 180*d) r.cr.Arc(x+a, y+a, a, 180*d, 270*d) r.cr.ClosePath() r.SetHex(color) r.cr.FillPreserve() r.SetHex(border) r.cr.Stroke() } func (r *Renderer) DrawText(x float64, y float64, text string, fontFamily string, fontSize float64, color int) { layout := pango.CairoCreateLayout(r.cr) font := pango.FontDescriptionFromString("sans normal 16") font.SetFamily(fontFamily) layout.SetFontDescription(font) layout.SetText(text, len(text)) r.SetHex(color) r.cr.MoveTo(x, y) pango.CairoUpdateLayout(r.cr, layout) pango.CairoShowLayout(r.cr, layout) } func (r *Renderer) DrawWrappedText(x float64, y float64, w float64, text string, fontFamily string, fontSize float64, color int) { layout := pango.CairoCreateLayout(r.cr) layout.SetWidth(int(w) * pango.SCALE) font := pango.FontDescriptionFromString("sans normal 16") font.SetFamily(fontFamily) layout.SetFontDescription(font) layout.SetText(text, len(text)) r.SetHex(color) r.cr.MoveTo(x, y) pango.CairoUpdateLayout(r.cr, layout) pango.CairoShowLayout(r.cr, layout) } func (r *Renderer) DrawButton(x float64, y float64, w float64, h float64, text string, fontFamily string, fontSize float64) { r.DrawFilledRoundedRectangle(x, y, w-4, h-4, w/h, 10, 0x000000) if r.mouseX > x && r.mouseX < x+w && r.mouseY > y && r.mouseY < y+h { r.DrawRoundedRectangle(x, y, w-4, h-4, w/h, 10, 0x00ff00) } else { r.DrawRoundedRectangle(x, y, w-4, h-4, w/h, 10, 0xffffff) } layout := pango.CairoCreateLayout(r.cr) font := pango.FontDescriptionFromString("sans normal 16") font.SetFamily(fontFamily) layout.SetFontDescription(font) layout.SetText(text, len(text)) textWidth, textHeight := layout.GetSize() textWidth /= pango.SCALE textHeight /= pango.SCALE r.SetHex(0xffffff) r.cr.MoveTo(x+(w-4-float64(textWidth))/2, y+(h-4-float64(textHeight))/2) pango.CairoUpdateLayout(r.cr, layout) pango.CairoShowLayout(r.cr, layout) } func (r *Renderer) DrawTextInput(x float64, y float64, w float64, h float64, text string, fontFamily string, fontSize float64, inFocus bool) { if r.mouseX > x && r.mouseX < x+w && r.mouseY > y && r.mouseY < y+h { r.DrawFilledRectangle(x, y+h-6, w-4, 2, 0x00ffff) } else { r.DrawFilledRectangle(x, y+h-6, w-4, 2, 0xffffff) } layout := pango.CairoCreateLayout(r.cr) font := pango.FontDescriptionFromString("sans normal 16") font.SetFamily(fontFamily) layout.SetFontDescription(font) layout.SetText(text, len(text)) textWidth, textHeight := layout.GetSize() textWidth /= pango.SCALE textHeight /= pango.SCALE r.SetHex(0xffffff) r.cr.MoveTo(x+(w-float64(textWidth))/2, y+(h-12-float64(textHeight))/2) pango.CairoUpdateLayout(r.cr, layout) pango.CairoShowLayout(r.cr, layout) if inFocus && r.delta%1600 < 800 { r.DrawFilledRectangle(x+(w+float64(textWidth))/2, y+4, 2, 20, 0xffffff) } } func (r *Renderer) DrawPlayers() { for _, item := range r.state.CurrentGame.PlayerNetObjects.ByPlayerClientID { var t innernetobjects.InnerNetObject = *r.state.CurrentGame.NetObjects[item.PlayerNetworkTransform] a, ok := t.(*innernetobjects.PlayerNetworkTransform) if ok { x := r.w/2 + float64(a.TargetPosition.X)*50 y := r.h/2 - float64(a.TargetPosition.Y)*50 r.DrawFilledRectangle(float64(x-5), float64(y-5), 10, 10, enum.GetPlayerMainColor(int(item.PlayerColor))) r.DrawText(x, y, strconv.FormatFloat(float64(a.TargetPosition.X), 'e', 4, 64)+strconv.FormatFloat(float64(a.TargetPosition.Y), 'e', 4, 64), defaultRenderFont, 10, 0xffffff) } } }