diff --git a/cloth.go b/cloth.go index d96a3d1..934feb8 100644 --- a/cloth.go +++ b/cloth.go @@ -39,6 +39,11 @@ func (c *Cloth) Init(posX, posY int, hud *Hud) { clothX := c.width / c.spacing clothY := c.height / c.spacing + // Skip the cloth initialization when the window is resized. + if c.isInitialized { + return + } + for y := 0; y <= clothY; y++ { for x := 0; x <= clothX; x++ { px := posX + x*c.spacing diff --git a/hud.go b/hud.go index 5172120..571ca73 100644 --- a/hud.go +++ b/hud.go @@ -28,22 +28,25 @@ type ( ) type Hud struct { - hudTag struct{} - panelInit time.Time - ctrlBtn *Easing - sliders map[int]*slider - slide *Easing - reset widget.Clickable - debug widget.Bool - list layout.List - activator gesture.Click - closer gesture.Click - width int - height int - closeBtn int - btnSize int - controls gesture.Hover - isActive bool + hudTag struct{} + panelInit time.Time + panelWidth int + panelHeight int + winOffsetX float64 // stores the X offset on window horizontal resize + winOffsetY float64 // stores the Y offset on window vertical resize + ctrlBtn *Easing + sliders map[int]*slider + slide *Easing + reset widget.Clickable + debug widget.Bool + list layout.List + activator gesture.Click + closer gesture.Click + closeBtn int + btnSize int + controls gesture.Hover + isActive bool + showHelp bool } type slider struct { @@ -89,10 +92,8 @@ func (h *Hud) addSlider(index int, s slider) { h.sliders[index] = &s } -// ShowHideControlsArea is responsible for showing or hiding the HUD control elements. -// After hovering the mouse over the bottom part of the window a certain amount of time -// it shows the HUD control by invoking an easing function. -func (h *Hud) ShowHideControlsArea(gtx layout.Context, th *material.Theme, m *Mouse, isActive bool) { +// ShowControlPanel is responsible for showing or hiding the HUD control elements. +func (h *Hud) ShowControlPanel(gtx layout.Context, th *material.Theme, m *Mouse, isActive bool) { if h.reset.Pressed() { for _, s := range h.sliders { s.widget.Value = s.value @@ -100,7 +101,7 @@ func (h *Hud) ShowHideControlsArea(gtx layout.Context, th *material.Theme, m *Mo } progress := h.slide.Update(gtx, isActive) - pos := h.slide.Animate(progress) * float64(h.height) + pos := h.slide.Animate(progress) * float64(h.panelHeight) // This offset will apply to the rest of the content laid out in this function. defer op.Offset(image.Pt(0, gtx.Constraints.Max.Y+h.closeBtn-int(pos))).Push(gtx.Ops).Pop() @@ -182,11 +183,11 @@ func (h *Hud) ShowHideControlsArea(gtx layout.Context, th *material.Theme, m *Mo pointer.CursorPointer.Add(gtx.Ops) /* Draw HUD Contents */ - sectionWidth := gtx.Dp(unit.Dp(h.width / 3)) + sectionWidth := gtx.Dp(unit.Dp(h.panelWidth / 3)) layout.Flex{ Spacing: layout.SpaceEnd, }.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { + layout.Rigid(func(gtx C) D { gtx.Constraints.Min.X = sectionWidth gtx.Constraints.Max.X = sectionWidth layout := layout.UniformInset(unit.Dp(10)).Layout(gtx, func(gtx C) D { @@ -201,30 +202,30 @@ func (h *Hud) ShowHideControlsArea(gtx layout.Context, th *material.Theme, m *Mo return D{} }) }) - h.height = layout.Size.Y + h.closeBtn + h.panelHeight = layout.Size.Y + h.closeBtn return layout }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { + layout.Rigid(func(gtx C) D { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { + layout.Rigid(func(gtx C) D { return layout.UniformInset(unit.Dp(5)).Layout(gtx, material.CheckBox(th, &h.debug, "Show Frame Rates").Layout) }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { + layout.Rigid(func(gtx C) D { return layout.UniformInset(unit.Dp(10)).Layout(gtx, material.Button(th, &h.reset, "Reset").Layout) }), ) }), - layout.Flexed(1, func(gtx layout.Context) layout.Dimensions { + layout.Flexed(1, func(gtx C) D { w := material.Body1(th, fmt.Sprintf("2D Cloth Simulation %s\nCopyright © 2023, Endre Simo", Version)) w.Alignment = text.End w.Color = th.ContrastBg w.TextSize = 12 - txtOffs := h.height - (3 * h.closeBtn) + txtOffs := h.panelHeight - (3 * h.closeBtn) defer op.Offset(image.Point{Y: txtOffs}).Push(gtx.Ops).Pop() return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { + layout.Rigid(func(gtx C) D { return layout.UniformInset(unit.Dp(10)).Layout(gtx, w.Layout) }), ) @@ -235,7 +236,7 @@ func (h *Hud) ShowHideControlsArea(gtx layout.Context, th *material.Theme, m *Mo // DrawCtrlBtn draws the button which activates the main HUD area with the sliders. func (h *Hud) DrawCtrlBtn(gtx layout.Context, th *material.Theme, m *Mouse, isActive bool) { progress := h.slide.Update(gtx, isActive) - pos := h.slide.Animate(progress) * float64(h.height) + pos := h.slide.Animate(progress) * float64(h.panelHeight) offset := gtx.Dp(unit.Dp(60)) offStack := op.Offset(image.Pt(0, gtx.Constraints.Max.Y-offset+int(pos))).Push(gtx.Ops) diff --git a/main.go b/main.go index bd85f05..7927901 100644 --- a/main.go +++ b/main.go @@ -35,10 +35,10 @@ var ( windowHeight = 580 // Gio Ops related variables - ops op.Ops - initTime time.Time - deltaTime time.Duration - sy unit.Dp + ops op.Ops + initTime time.Time + deltaTime time.Duration + mouseScrollY unit.Dp // pprof related variables profile string @@ -62,7 +62,7 @@ func main() { app.Title("Gio - 2D Cloth Simulation"), app.Size(unit.Dp(windowWidth), unit.Dp(windowHeight)), ) - w.Perform(system.ActionMaximize) + //w.Perform(system.ActionMaximize) if err := loop(w); err != nil { log.Fatal(err) } @@ -106,7 +106,7 @@ func loop(w *app.Window) error { case system.FrameEvent: start := hrtime.Now() gtx := layout.NewContext(&ops, e) - hud.width = windowWidth + hud.panelWidth = windowWidth hud.btnSize = gtx.Dp(unit.Dp(40)) hud.closeBtn = gtx.Dp(unit.Dp(25)) @@ -136,7 +136,7 @@ func loop(w *app.Window) error { key.InputOp{ Tag: &keyTag, - Keys: key.NameEscape + "|" + key.NameCtrl + "|" + key.NameAlt + "|" + key.NameSpace, + Keys: key.NameEscape + "|" + key.NameCtrl + "|" + key.NameAlt + "|" + key.NameSpace + "|" + key.NameF1, }.Add(gtx.Ops) if mouse.getLeftButton() { @@ -156,8 +156,11 @@ func loop(w *app.Window) error { for _, ev := range gtx.Queue.Events(&keyTag) { if e, ok := ev.(key.Event); ok { if e.State == key.Press { - if e.Name == key.NameSpace { + switch e.Name { + case key.NameSpace: resetCloth() + case key.NameF1: + hud.showHelp = !hud.showHelp } } if e.Name == key.NameEscape { @@ -166,11 +169,24 @@ func loop(w *app.Window) error { } } - // Reset cloth on window resize. + // Reset the window offsets on resize. + hud.winOffsetX = 0 + hud.winOffsetY = 0 + + if e.Size.X != windowWidth { + hud.winOffsetX = float64(e.Size.X-windowWidth) * 0.5 + } + if e.Size.Y != windowHeight { + hud.winOffsetY = float64(e.Size.Y-windowHeight) * 0.25 + } + if e.Size.X != windowWidth || e.Size.Y != windowHeight { + cloth.Init(windowWidth, windowHeight, hud) + windowWidth = e.Size.X windowHeight = e.Size.Y - resetCloth() + cloth.width = windowWidth + cloth.height = windowHeight } fillBackground(gtx, color.NRGBA{R: 0xf2, G: 0xf2, B: 0xf2, A: 0xff}) @@ -207,13 +223,13 @@ func loop(w *app.Window) error { key.FocusOp{Tag: keyTag}.Add(gtx.Ops) switch ev.Type { case pointer.Scroll: - sy += unit.Dp(ev.Scroll.Y) - if sy < minFocusArea { - sy = minFocusArea - } else if sy > mouse.maxScrollY { - sy = mouse.maxScrollY + mouseScrollY += unit.Dp(ev.Scroll.Y) + if mouseScrollY < minFocusArea { + mouseScrollY = minFocusArea + } else if mouseScrollY > mouse.maxScrollY { + mouseScrollY = mouse.maxScrollY } - mouse.setScrollY(sy) + mouse.setScrollY(mouseScrollY) case pointer.Move: pos := mouse.getCurrentPosition(ev) mouse.updatePosition(float64(pos.X), float64(pos.Y)) @@ -247,7 +263,6 @@ func loop(w *app.Window) error { } } } - cloth.Update(gtx, mouse, hud, delta) return layout.Dimensions{} }), @@ -282,7 +297,7 @@ func loop(w *app.Window) error { } } hud.DrawCtrlBtn(gtx, th, mouse, hud.isActive) - hud.ShowHideControlsArea(gtx, th, mouse, hud.isActive) + hud.ShowControlPanel(gtx, th, mouse, hud.isActive) return layout.Dimensions{} }), diff --git a/particle.go b/particle.go index 71afccd..8b67efc 100644 --- a/particle.go +++ b/particle.go @@ -13,7 +13,7 @@ import ( const ( clothPinDist = 4 defFocusArea = 50 - minFocusArea = 20 + minFocusArea = 30 maxFocusArea = 120 maxDragForce = 20 ) @@ -50,7 +50,6 @@ func NewParticle(x, y float64, hud *Hud, col color.NRGBA) *Particle { // Update updates the particle system using the Verlet integration. func (p *Particle) Update(gtx layout.Context, mouse *Mouse, hud *Hud, delta float64) { - //p.draw(gtx, float32(p.x), float32(p.y), 2) p.update(gtx, mouse, hud, delta) } @@ -86,6 +85,11 @@ func (p *Particle) update(gtx layout.Context, mouse *Mouse, hud *Hud, dt float64 tearDistance := float64(hud.sliders[3].widget.Value) if p.pinX { + // Recalculate the pinned particles position when the window is resized. + // We need to do this only for the pinned particles, because the rest + // of the particles will just adjust themselves. + p.x += hud.winOffsetX + p.y += hud.winOffsetY return }