cogentcore / core

A free and open source framework for building powerful, fast, elegant 2D and 3D apps that run on macOS, Windows, Linux, iOS, Android, and the web with a single Go codebase, allowing you to Code Once, Run Everywhere.
http://cogentcore.org/core
BSD 3-Clause "New" or "Revised" License
1.74k stars 82 forks source link

Fine grained movements of widgets in a frame with no layout. #1186

Open DCowboy opened 2 months ago

DCowboy commented 2 months ago

Describe the feature

After failing to make what I was interested in clear enough for an off-handed question, and not finding any examples in cogent/cogent that did anything in the realm of what I was looking to do, It was suggested I make a new issue with full details to see the best way to go about it if a good way exists within what's currently supported or to request the feature be supported if need be.

In my specific use case, I'm interested in making a ticker made of buttons within a frame. While I do understand that Cogent is designed differently than other GUI libraries to be smaller faster and more responsive, I only currently know how to explain it in terms of how those other libraries work. So, please bear with me on that.

Using other libraries, this would usually be controlled by defining a tick() function that:

  1. Changed the positions of all the buttons in a slice or map, (as buttons could be added or removed as needed) to the left by a speed amount.
  2. If the left most button was entirely past the frame's border, and supposedly completely out view as a result, set that button's position to the last button's position + width+speed, (so that it sits right up against the button now preceding it,)
  3. Call the update function to reflect the changes.

If this were put on a loop, (probably best implemented in a goroutine; given the way Cogent looks to be designed,) it would show continuous movement.

There are lots of use cases where changing a widget's position, size, (or shape- mostly in the case of canvas items,) could be desirable in a Graphic User Interface; whether the changes are brought about by a looped function for constant movement, or another widget like a button's function, or even a key bind.

Is there a good way to go about this type of thing in the current infrastructure?

Update: Upon trying to make things work under the current structure, I seem to have run into an issue just setting initial positions Using NoLayout in a frame that's also using Maker.

Perhaps I'm doing something wrong. I tried a few different ways on my end before heading tot the playground to see if setting up an even simpler version starting from the tree example would work. Yet I got the same results. Here is the playground version:

package main

func main() {
    b := core.NewBody()
    btnWidth := float32(50)
    number := 3
    spinner := core.Bind(&number, core.NewSpinner(b)).SetMin(0)
    buttons := core.NewFrame(b)
    buttons.Styler(func(s *styles.Style) {
        s.Display = styles.NoLayout
        s.Overflow.Set(styles.OverflowHidden)
        s.Min.Set(units.Dp(300), units.Dp(50))
        s.Max.Set(units.Dp(300), units.Dp(50))
        s.Border.Style.SetAll(styles.BorderInset)
        s.Border.Width.SetAll(units.Dp(2))
    })
    buttons.Maker(func(p *tree.Plan) {
        for i := range number {
            tree.AddAt(p, strconv.Itoa(i), func(w *core.Button) {
                w.Styler(func(s *styles.Style) {
                    s.Min.Set(units.Dp(btnWidth), units.Dp(20))
                    s.Max.Set(units.Dp(btnWidth), units.Dp(20))
                    //s.Padding.SetAll(units.Dp(0))
                    s.Pos.Set(units.Dp(btnWidth*float32(i)), units.Dp(0))
                    //s.Pos = units.XY{X: units.Dp(btnWidth*float32(i)), Y: units.Dp(0)}
                    core.MessageSnackbar(b, s.Pos.String())

                })
                w.SetText("btn " + strconv.Itoa(i))

            })
        }
    })
    spinner.OnChange(func(e events.Event) {
        buttons.Update()
    })
    b.RunMainWindow()
}

After I get them initially set up, using the Styler, I'll still have to figure out how to make it conditional so the positions can then be altered when updated. But so far, when in NoLayout, Only button 0 shows when number is greater than 0. I've added a snackbar to the playground to replace the Println I was using in my desktop editor to show at least for the last button, that the position should have been set.

Am I going about this the wrong way?
Or Would now be a good time to suggest a new other type frame widget? Perhaps with a name like XYFrame or a better name as I confess to not being the best at naming things. This frame could then handle all NoLayout scenarios and you could remove that option from regular frames.

XYFrame would automatically be NoLayout. It would need provide easy ways to access it's children's geometry to make it easy for controlling functions and/or the children themselves to reference. It would also be good if it assisted with functions to allow it's children to do things like:

  1. change position/move(new units.XY)
  2. resize (new units.XY)
  3. rotate(transform to change angle between 0 and 359 degrees - which would resize bounding box)
  4. check overlap (handy and seems to complete the set for basic things done with widgets that aren't static)
  5. Perhaps a couple other things I'm just not thinking of this late.

Note the things above are also precursors to the the functions that would also be needed for movement for the X and Y axis movement in XYZ.

Sorry for the long-windedness with this update, but I figured since I was thinking about making the suggestion for an easier way to do things, might as well try to make the suggestion as complete as possible for most common use cases of non-static GUI elements.

Relevant code

No response

kkoreilly commented 2 months ago

Thank you for filing this issue and providing this update, and apologies for the late response. I will look at your code and get back to you soon.

DCowboy commented 2 months ago

No worries about late responses. I realize that you're busy. I can be patient since my project still has a lot of back end work that needs done too.

kkoreilly commented 2 months ago

I discovered that we actually don't have any code implementing s.Pos currently, which is why it isn't working. We did at some point and it shouldn't be too hard to implement again. This isn't our highest priority, but we will do it at some point relatively soon, and I can help you get your code working after that. Feel free to ping us again once you have finished more of your back-end work.

Also, just in case you don't already know, if you are making something like a game, you might be better suited using a canvas or SVG widget, in which it is very easy to do these kind of fine-grained dynamic manipulations.

DCowboy commented 2 months ago

I discovered that we actually don't have any code implementing s.Pos currently, which is why it isn't working. We did at some point and it shouldn't be too hard to implement again. This isn't our highest priority, but we will do it at some point relatively soon, and I can help you get your code working after that. Feel free to ping us again once you have finished more of your back-end work.

Also, just in case you don't already know, if you are making something like a game, you might be better suited using a canvas or SVG widget, in which it is very easy to do these kind of fine-grained dynamic manipulations.

I understood that. I explained above that my use case is making a ticker. Specifically, one that will be used for a crypto trading tool. reducing multiple browser tabs of information to a small window.

kkoreilly commented 2 months ago

Okay, I understand. We will definitely get this working when we have the time.