diff --git a/cmd/bedrocktool/main.go b/cmd/bedrocktool/main.go index 56bed96..e6d3ceb 100644 --- a/cmd/bedrocktool/main.go +++ b/cmd/bedrocktool/main.go @@ -12,6 +12,7 @@ import ( "time" "github.com/bedrock-tool/bedrocktool/locale" + "github.com/bedrock-tool/bedrocktool/ui/messages" "github.com/bedrock-tool/bedrocktool/utils" "gopkg.in/square/go-jose.v2/json" @@ -80,17 +81,6 @@ func main() { logrus.Infof(locale.Loc("bedrocktool_version", locale.Strmap{"Version": utils.Version})) } - go func() { - newVersion, err := utils.Updater.UpdateAvailable() - if err != nil { - logrus.Error(err) - } - - if newVersion != "" && utils.Version != "" { - logrus.Infof(locale.Loc("update_available", locale.Strmap{"Version": newVersion})) - } - }() - ctx, cancel := context.WithCancel(context.Background()) flag.StringVar(&utils.RealmsEnv, "realms-env", "", "realms env") @@ -116,6 +106,23 @@ func main() { ui = &CLI{} } + utils.CurrentUI = ui + + if utils.Version != "" { + go func() { + newVersion, err := utils.Updater.UpdateAvailable() + if err != nil { + logrus.Error(err) + } + + if newVersion != "" { + logrus.Infof(locale.Loc("update_available", locale.Strmap{"Version": newVersion})) + utils.UpdateAvailable = newVersion + ui.Message(messages.UpdateAvailable{Version: newVersion}) + } + }() + } + // exit cleanup sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) diff --git a/go.mod b/go.mod index 4eeeda3..f2f0aee 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( golang.design/x/lockfree v0.0.1 golang.org/x/crypto v0.8.0 golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 + golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 golang.org/x/oauth2 v0.7.0 golang.org/x/text v0.9.0 gopkg.in/square/go-jose.v2 v2.6.0 @@ -69,7 +70,6 @@ require ( github.com/tklauser/numcpus v0.6.0 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect go.uber.org/atomic v1.10.0 // indirect - golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 // indirect golang.org/x/image v0.7.0 // indirect golang.org/x/mod v0.8.0 // indirect golang.org/x/net v0.9.0 // indirect diff --git a/subcommands/skins/skins.go b/subcommands/skins/skins.go index d0bc7b0..f2e3504 100644 --- a/subcommands/skins/skins.go +++ b/subcommands/skins/skins.go @@ -45,6 +45,10 @@ func (c *SkinCMD) Execute(ctx context.Context, ui utils.UI) error { OnClientConnect: func(conn minecraft.IConn) { ui.Message(messages.SetUIState(messages.UIStateConnecting)) }, + OnServerConnect: func() (cancel bool) { + ui.Message(messages.SetUIState(messages.UIStateMain)) + return false + }, }) if proxy.WithClient { diff --git a/subcommands/world/world.go b/subcommands/world/world.go index 40b987b..5598d3a 100644 --- a/subcommands/world/world.go +++ b/subcommands/world/world.go @@ -55,7 +55,6 @@ func (c *WorldCMD) Execute(ctx context.Context, ui utils.UI) error { SaveImage: c.SaveImage, })) - ui.Message(messages.SetUIState(messages.UIStateConnect)) err = proxy.Run(ctx, serverAddress, hostname) if err != nil { return err diff --git a/ui/gui.go b/ui/gui.go index 6d4dd8b..4ae007d 100644 --- a/ui/gui.go +++ b/ui/gui.go @@ -17,6 +17,7 @@ import ( "github.com/bedrock-tool/bedrocktool/ui/gui/pages/packs" "github.com/bedrock-tool/bedrocktool/ui/gui/pages/settings" "github.com/bedrock-tool/bedrocktool/ui/gui/pages/skins" + "github.com/bedrock-tool/bedrocktool/ui/gui/pages/update" "github.com/bedrock-tool/bedrocktool/ui/gui/pages/worlds" "github.com/bedrock-tool/bedrocktool/ui/messages" "github.com/bedrock-tool/bedrocktool/utils" @@ -75,6 +76,7 @@ func (g *GUI) Start(ctx context.Context, cancel context.CancelFunc) (err error) g.router.Register("worlds", worlds.New(&g.router)) g.router.Register("skins", skins.New(&g.router)) g.router.Register("packs", packs.New(&g.router)) + g.router.Register("update", update.New(&g.router)) g.router.SwitchTo("Settings") diff --git a/ui/gui/icons/main.go b/ui/gui/icons/main.go new file mode 100644 index 0000000..fc1e0a6 --- /dev/null +++ b/ui/gui/icons/main.go @@ -0,0 +1,16 @@ +package icons + +import ( + "gioui.org/widget" + "golang.org/x/exp/shiny/materialdesign/icons" +) + +func mustIcon(data []byte) widget.Icon { + ic, err := widget.NewIcon(data) + if err != nil { + panic(err) + } + return *ic +} + +var ActionUpdate = mustIcon(icons.ActionUpdate) diff --git a/ui/gui/pages/packs/packs.go b/ui/gui/pages/packs/packs.go index d4e0b0e..4497f10 100644 --- a/ui/gui/pages/packs/packs.go +++ b/ui/gui/pages/packs/packs.go @@ -96,7 +96,6 @@ func drawPackIcon(gtx C, hasImage bool, imageOp paint.ImageOp, bounds image.Poin } return D{Size: bounds} }) - } func MulAlpha(c color.NRGBA, alpha uint8) color.NRGBA { diff --git a/ui/gui/pages/page.go b/ui/gui/pages/page.go index 45d6187..58ed7bf 100644 --- a/ui/gui/pages/page.go +++ b/ui/gui/pages/page.go @@ -8,9 +8,12 @@ import ( "gioui.org/layout" "gioui.org/op/paint" + "gioui.org/widget" "gioui.org/widget/material" "gioui.org/x/component" "github.com/bedrock-tool/bedrocktool/ui/messages" + "github.com/bedrock-tool/bedrocktool/utils" + "github.com/sirupsen/logrus" ) type HandlerFunc = func(data interface{}) messages.MessageResponse @@ -39,6 +42,8 @@ type Router struct { *component.AppBar *component.ModalLayer NonModalDrawer, BottomBar bool + + UpdateButton *widget.Clickable } func NewRouter(ctx context.Context, invalidate func(), th *material.Theme) Router { @@ -63,6 +68,8 @@ func NewRouter(ctx context.Context, invalidate func(), th *material.Theme) Route ModalNavDrawer: modalNav, AppBar: bar, NavAnim: na, + + UpdateButton: &widget.Clickable{}, } } @@ -90,6 +97,10 @@ func (r *Router) SwitchTo(tag string) { } func (r *Router) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions { + if r.UpdateButton.Clicked() { + r.SwitchTo("update") + } + for _, event := range r.AppBar.Events(gtx) { switch event := event.(type) { case component.AppBarNavigationClicked: @@ -141,6 +152,18 @@ func (r *Router) Handler(data interface{}) messages.MessageResponse { return messages.MessageResponse{} } +func (r *Router) Execute(cmd utils.Command) { + r.Wg.Add(1) + go func() { + defer r.Wg.Done() + + err := cmd.Execute(r.Ctx, utils.CurrentUI) + if err != nil { + logrus.Error(err) + } + }() +} + var Pages = map[string]func(*Router) Page{} func Register(name string, fun func(*Router) Page) { diff --git a/ui/gui/pages/settings/settings.go b/ui/gui/pages/settings/settings.go index 1145f49..7c57b3c 100644 --- a/ui/gui/pages/settings/settings.go +++ b/ui/gui/pages/settings/settings.go @@ -9,6 +9,7 @@ import ( "gioui.org/widget" "gioui.org/widget/material" "gioui.org/x/component" + "github.com/bedrock-tool/bedrocktool/ui/gui/icons" "github.com/bedrock-tool/bedrocktool/ui/gui/pages" "github.com/bedrock-tool/bedrocktool/ui/gui/settings" "github.com/bedrock-tool/bedrocktool/ui/messages" @@ -33,6 +34,8 @@ type Page struct { } startButton widget.Clickable + + actions []component.AppBarAction } func New(router *pages.Router) *Page { @@ -50,6 +53,10 @@ func New(router *pages.Router) *Page { p.cmdMenu.items = make(map[string]*widget.Clickable, len(utils.ValidCMDs)) options := make([]func(layout.Context) layout.Dimensions, 0, len(utils.ValidCMDs)) for _, name := range cmdNames { + if _, ok := settings.Settings[name]; !ok { + continue + } + item := &widget.Clickable{} p.cmdMenu.items[name] = item options = append(options, component.MenuItem(router.Theme, item, name).Layout) @@ -70,7 +77,7 @@ func New(router *pages.Router) *Page { var _ pages.Page = &Page{} func (p *Page) Actions() []component.AppBarAction { - return []component.AppBarAction{} + return p.actions } func (p *Page) Overflow() []component.OverflowAction { @@ -97,16 +104,7 @@ func (p *Page) Layout(gtx C, th *material.Theme) D { } p.Router.SwitchTo(p.cmdMenu.selected) - - p.Router.Wg.Add(1) - go func() { - defer p.Router.Wg.Done() - - err := cmd.Execute(p.Router.Ctx, utils.CurrentUI) - if err != nil { - logrus.Error(err) - } - }() + p.Router.Execute(cmd) } } @@ -173,7 +171,17 @@ func (p *Page) Layout(gtx C, th *material.Theme) D { }) } -func (p *Page) Handler(any) messages.MessageResponse { +func (p *Page) Handler(m any) messages.MessageResponse { + switch m.(type) { + case messages.UpdateAvailable: + p.actions = []component.AppBarAction{ + component.SimpleIconAction(p.Router.UpdateButton, &icons.ActionUpdate, component.OverflowAction{}), + } + + p.Router.AppBar.SetActions(p.actions, nil) + p.Router.Invalidate() + } + return messages.MessageResponse{ Ok: false, Data: nil, diff --git a/ui/gui/pages/update/update.go b/ui/gui/pages/update/update.go new file mode 100644 index 0000000..27a4a78 --- /dev/null +++ b/ui/gui/pages/update/update.go @@ -0,0 +1,107 @@ +package update + +import ( + "fmt" + + "gioui.org/layout" + "gioui.org/unit" + "gioui.org/widget" + "gioui.org/widget/material" + "gioui.org/x/component" + "github.com/bedrock-tool/bedrocktool/ui/gui/pages" + "github.com/bedrock-tool/bedrocktool/ui/messages" + "github.com/bedrock-tool/bedrocktool/utils" +) + +type ( + C = layout.Context + D = layout.Dimensions +) + +type Page struct { + *pages.Router + + State messages.UIState + startButton widget.Clickable + err error + updating bool +} + +func New(router *pages.Router) *Page { + return &Page{ + Router: router, + State: messages.UIStateMain, + } +} + +var _ pages.Page = &Page{} + +func (p *Page) Actions() []component.AppBarAction { + return []component.AppBarAction{} +} + +func (p *Page) Overflow() []component.OverflowAction { + return []component.OverflowAction{} +} + +func (p *Page) NavItem() component.NavItem { + return component.NavItem{ + Name: "Update", + //Icon: icon.OtherIcon, + } +} + +func (p *Page) Layout(gtx C, th *material.Theme) D { + if p.startButton.Clicked() && !p.updating { + p.updating = true + go func() { + p.err = utils.Updater.Update() + if p.err == nil { + p.State = messages.UIStateFinished + } + p.updating = false + p.Router.Invalidate() + }() + } + + return layout.Inset{ + Top: unit.Dp(25), + Bottom: unit.Dp(25), + Right: unit.Dp(35), + Left: unit.Dp(35), + }.Layout(gtx, func(gtx C) D { + if p.err != nil { + return layout.Center.Layout(gtx, material.H1(th, p.err.Error()).Layout) + } + if p.updating { + return layout.Center.Layout(gtx, material.H3(th, "Updating...").Layout) + } + switch p.State { + case messages.UIStateMain: + // show the main ui + return layout.Flex{Axis: layout.Vertical}.Layout(gtx, + layout.Rigid(material.Label(th, 20, fmt.Sprintf("Current: %s\nNew: %s", utils.Version, utils.UpdateAvailable)).Layout), + layout.Rigid(material.Button(th, &p.startButton, "Do Update").Layout), + ) + case messages.UIStateFinished: + return layout.Center.Layout(gtx, func(gtx C) D { + return layout.Flex{Axis: layout.Vertical}.Layout(gtx, + layout.Rigid(material.H3(th, "Update Finished").Layout), + layout.Rigid(func(gtx C) D { + return layout.Center.Layout(gtx, material.Label(th, th.TextSize, "restart the app").Layout) + }), + ) + }) + } + + return D{} + }) +} + +func (p *Page) Handler(data interface{}) messages.MessageResponse { + r := messages.MessageResponse{ + Ok: false, + Data: nil, + } + return r +} diff --git a/ui/gui/pages/worlds/map.go b/ui/gui/pages/worlds/map.go index 527275d..e0342af 100644 --- a/ui/gui/pages/worlds/map.go +++ b/ui/gui/pages/worlds/map.go @@ -41,7 +41,7 @@ func (m *Map) HandlePointerEvent(e pointer.Event) { case pointer.Release: m.grabbed = false case pointer.Scroll: - scaleFactor := -float32(math.Pow(1.01, float64(e.Scroll.Y))) + scaleFactor := float32(math.Pow(1.01, float64(e.Scroll.Y))) m.transform = m.transform.Scale(e.Position.Sub(m.center), f32.Pt(scaleFactor, scaleFactor)) m.scaleFactor *= scaleFactor } @@ -122,8 +122,7 @@ func (m *Map) Update(u *messages.UpdateMap) { } } else { for _, pos := range u.UpdatedTiles { - tile := u.Tiles[pos] - drawTile(m.MapImage, m.BoundsMin, pos, tile) + drawTile(m.MapImage, m.BoundsMin, pos, u.Tiles[pos]) } } diff --git a/ui/gui/pages/worlds/worlds.go b/ui/gui/pages/worlds/worlds.go index 26fe626..33bb4fe 100644 --- a/ui/gui/pages/worlds/worlds.go +++ b/ui/gui/pages/worlds/worlds.go @@ -91,9 +91,7 @@ func (p *Page) Layout(gtx C, th *material.Theme) D { return layout.Center.Layout(gtx, material.Label(th, 100, "Connecting").Layout) case messages.UIStateMain: // show the main ui - return layout.Flex{ - Axis: layout.Vertical, - }.Layout(gtx, + return layout.Flex{Axis: layout.Vertical}.Layout(gtx, //layout.Rigid(material.Label(th, th.TextSize, p.worldName).Layout), layout.Flexed(1, func(gtx C) D { return layout.Center.Layout(gtx, p.worldMap.Layout) diff --git a/ui/messages/messages.go b/ui/messages/messages.go index 04cac24..a975513 100644 --- a/ui/messages/messages.go +++ b/ui/messages/messages.go @@ -96,3 +96,7 @@ type DownloadedPack struct { type FinishedDownloadingPacks struct { Packs []*DownloadedPack } + +type UpdateAvailable struct { + Version string +} diff --git a/utils/proxy.go b/utils/proxy.go index be82234..df1ba94 100644 --- a/utils/proxy.go +++ b/utils/proxy.go @@ -15,6 +15,7 @@ import ( "time" "github.com/bedrock-tool/bedrocktool/locale" + "github.com/bedrock-tool/bedrocktool/ui/messages" "github.com/repeale/fp-go" "github.com/sandertv/gophertunnel/minecraft" "github.com/sandertv/gophertunnel/minecraft/protocol" @@ -390,6 +391,7 @@ func (p *ProxyContext) Run(ctx context.Context, serverAddress, name string) (err var cdp *login.ClientData = nil if p.WithClient && !isReplay { + CurrentUI.Message(messages.SetUIState(messages.UIStateConnect)) err = p.connectClient(ctx, serverAddress, &cdp) if err != nil { return err diff --git a/utils/updater.go b/utils/updater.go index efa428c..bd5b140 100644 --- a/utils/updater.go +++ b/utils/updater.go @@ -15,6 +15,8 @@ import ( var Version string var CmdName = "bedrocktool" +var UpdateAvailable string + const updateServer = "https://updates.yuv.pink/" type trequester struct {