
-----------------------------------
Clayton
Sat Dec 09, 2006 10:40 am

Custom Buttons
-----------------------------------
I was asked by a friend the other day why he couldn't pass procedures with parameters when he created a button in Turing. I told him quite plainly that Turing's built in GUI sucked. So he asked me how to get around it. I told him to make one himself, he claimed he didn't know how, so I did. Heres the source with an example program:


class Buttons
    import Mouse
    export Pressed, Create, CreateFull, Refresh, EnableRollover

    var x, y, width, height : int
    var text : string
    var rollover_enabled : boolean := false
    var rollover_color : int
    const font := Font.New ("times new roman:12")

    %Mouse Variables to keep track of whether the button is being pressed or not
    var oldbutton : int := 0

    procedure Create (x_, y_ : int, text_ : string)
        x := x_
        y := y_
        text := text_
        width := Font.Width (text, font) + 10
        height := 16
        Draw.Box (x, y, x + width, y + height, black)
        Draw.Fill (x + 1, y + 1, grey, black)
        Font.Draw (text, x + (width div 2) - (Font.Width (text, font) div 2), y + (height div 2 - 4), font, black)
    end Create

    procedure CreateFull (x_, y_, width_, height_ : int, text_ : string)
        text := text_
        x := x_
        y := y_
        width := width_
        height := height_
        Draw.Box (x, y, x + width, y + height, black)
        Draw.Fill (x + 1, y + 1, grey, black)
        Font.Draw (text, x + (width div 2) - (Font.Width (text, font) div 2), y + (height div 2 - 4), font, black)
    end CreateFull

    fcn MouseHover : boolean
        var mx, my, mb : int
        Mouse.Where (mx, my, mb)
        if mx >= x and my >= y and mx  CreateFull (maxx div 2, maxy div 2, 300, 100, "Hello World Happy Gilmore")
button2 -> Create (5, 5, "Button 2")
button1 -> EnableRollover (true, brightred)
button2 -> EnableRollover (true, purple)
loop
    if button1 -> Pressed then
        Draw.FillBox (0, 0, maxx, maxy, brightred)
        View.Update
        Time.Delay (1000)
    end if
    if button2 -> Pressed then
        Draw.FillBox (0, 0, maxx, maxy, purple)
        View.Update
        Time.Delay (1000)
    end if
    button1 -> Refresh
    button2 -> Refresh
    View.Update
    Draw.Cls
    exit when hasch
end loop


a couple of procedures you should know about when using this:

Create (x, y : int, text : string)

Creates a button at (x,y) with text inside of it. The button will automatically become the width and height required for the text.

CreateFull (x, y, width, height : int, text : string)

Like Create, except you specify its width and height.

Pressed : boolean

Pressed simply returns true if the button has been pressed (ie the mouse button was released over it)

Refresh

Simply redraws the button on the screen again

EnableRollover (choice : boolean, Color : int)

Enables a rollover effect on the button (true for it to happen, false for it not to). With rollover enabled, the button will change color to Color when the mouse is over the button

Have fun with it, just remember to give credit to me if you use it.

NOTE: I will be making this a module later on so that more people will be able to use it.

EDIT: Any additions you would like to see are welcome! I'm always open to suggestions

-----------------------------------
Cervantes
Sat Dec 09, 2006 11:02 am

Re: Custom Buttons
-----------------------------------
I was asked by a friend the other day why he couldn't pass procedures with parameters when he created a button in Turing. I told him quite plainly that Turing's built in GUI sucked.
I'd just like to point out that there is a reason this can't be done. It's because each button is an object, and one of it's instance variables is an action procedure. That is, a procedure with no parameters. Turing is a typed language, and a procedure with one parameter has a different type than a procedure with no parameters. In the class for the button, the instance variable must be given a type. It's got to be either a procedure with no parameters, or a procedure with one parameter, or.... Thus, all buttons have to be consistant with each other. The best way to do this is to make all buttons use action procedures.

This wouldn't be much of a problem if Turing had support for [url=http://www.compsci.ca/v2/viewtopic.php?p=118316#118316]anonymous functions.

-----------------------------------
Clayton
Sat Dec 09, 2006 11:04 am


-----------------------------------
Aye, I know that. I was just explaining that too him (also didn't want to drone on with my post [Which I think I did anyway]). Any other suggestions though Cervantes?

-----------------------------------
Cervantes
Sat Dec 09, 2006 11:14 am


-----------------------------------
You've handled the buttons' actions the same way [url=http://www.compsci.ca/v2/viewtopic.php?t=9329]I did. I'm actually not so sure that's the best way to do it, anymore. It means having a really big main loop, because you've got to do all those if statements yourself. Can you find a way to make both approaches possible?

I'd also suggest giving the buttons some visual charactersitc that shows when they are pressed down. The button should look different when the mouse is pressing it than when the mouse is merely hovering over it.

In your Create procedure, you assigned a value to `height'. Why not assign that variable outside that procedure, right when you declare the variable? The code inside a class is run each time an object is created, and `self' is that object, so doing it this way would give you the same effect. It's not necessarily better, though. Just something to consider.

Here's something else to consider. Your code checks the state of the mouse each time the Pressed function is called. You'll be checking the state of the mouse once for each button you've got. That's not really necessary. We should only have to check the state of the mouse at most once each time through the loop. So, one possibility is to use a global variable for the state of the mouse, but we all hate global variables. Another possibility is to pass the state of the mouse into the Pressed function as a parameter. This could also give you the power to sort of fudge the position of the mouse if you wanted to, by passing in bogus values.

To show off the fact that your buttons don't act like the action-procedure slaves that Turing's GUI's buttons are, why don't you make a procedure that takes in a colour as a parameter and does your little "draw the screen that colour, View.Update, then delay(1000)"?

-----------------------------------
Clayton
Sat Dec 09, 2006 11:28 am


-----------------------------------
Thanks for the suggestions Cervantes, I'll be sure to try and get those points worked into there somehow. For now though, I have decided to get everything moved into a module (the class still exists, but the programmer doesn't deal directly with it). The modules' name has become Buttons for simplicity. the class name is now buttons instead. The code: (with the procedure Cervantes mentioned above)


class buttons
    import Mouse
    export Pressed, Create, CreateFull, Refresh, EnableRollover

    var x, y, width, height : int
    var text : string
    var rollover_enabled : boolean := false
    var rollover_color : int
    const font := Font.New ("times new roman:12")

    %Mouse Variables to keep track of whether the button is being pressed or not
    var oldbutton : int := 0

    procedure Create (x_, y_ : int, text_ : string)
        x := x_
        y := y_
        text := text_
        width := Font.Width (text, font) + 10
        height := 16
        Draw.Box (x, y, x + width, y + height, black)
        Draw.Fill (x + 1, y + 1, grey, black)
        Font.Draw (text, x + (width div 2) - (Font.Width (text, font) div 2), y + (height div 2 - 4), font, black)
    end Create

    procedure CreateFull (x_, y_, width_, height_ : int, text_ : string)
        text := text_
        x := x_
        y := y_
        width := width_
        height := height_
        Draw.Box (x, y, x + width, y + height, black)
        Draw.Fill (x + 1, y + 1, grey, black)
        Font.Draw (text, x + (width div 2) - (Font.Width (text, font) div 2), y + (height div 2 - 4), font, black)
    end CreateFull

    fcn MouseHover : boolean
        var mx, my, mb : int
        Mouse.Where (mx, my, mb)
        if mx >= x and my >= y and mx  Create (x, y, text)
        result upper (button_stack)
    end Create

    fcn CreateFull (x, y, width, height : int, text : string) : int
        new button_stack, upper (button_stack) + 1
        new buttons, button_stack (upper (button_stack))
        button_stack (upper (button_stack)) -> CreateFull (x, y, width, height, text)
        result upper (button_stack)
    end CreateFull

    procedure Refresh (button_id : int)
        button_stack (button_id) -> Refresh
    end Refresh

    procedure EnableRollover (button_id : int, choice : boolean, clr : int)
        button_stack (button_id) -> EnableRollover (choice, clr)
    end EnableRollover

    fcn Pressed (button_id : int) : boolean
        result button_stack (button_id) -> Pressed
    end Pressed
end Buttons

%Example Program
procedure fill_screen (clr : int)
    Draw.FillBox (0, 0, maxx, maxy, clr)
    View.Update
    Time.Delay (1000)
end fill_screen

View.Set ("graphics, offscreenonly")
var button1 : int := Buttons.CreateFull (maxx div 2, maxy div 2, 300, 100, "Hello World Happy Gilmore")
var button2 : int := Buttons.Create (5, 5, "Button 2")

Buttons.EnableRollover (button1, true, brightred)
Buttons.EnableRollover (button2, true, purple)
loop
    if Buttons.Pressed (button1) then
        fill_screen (brightred)
    end if
    if Buttons.Pressed (button2) then
        fill_screen (purple)
    end if
    Buttons.Refresh (button1)
    Buttons.Refresh (button2)
    View.Update
    Draw.Cls
    exit when hasch
end loop


Because the class is now being handled through a module, I had to find a way to get my procedures and functions to act upon seperate buttons (as opposed to all of them). So now Create and CreateFull are functions returning ints. This is your button id. The procedures breakdown like this:

Create (x, y : int, text : string) : int

Same as before, it just returns the buttons new id.

CreateFull (x, y, width, height : int, text : string) : int

Again, only thing that is different is that it returns a button id.

Refresh (button_id : int)

You now have to pass it the button id that you want it to refresh.

EnableRollover (button_id : int, choice : boolean, clr : int)

Just like the above, except you have to pass it the button id that you want to have the rollover.

Pressed (button_id : int) : boolean

Just pass it the button id now.

Any other suggestions, just say them.

-----------------------------------
Hackmaster
Sat Dec 09, 2006 3:56 pm


-----------------------------------
I would just like to point out that there is another way around the first stated problem, altough it's ugly, and quite frankly, your way is much better. but, for people who are new, and don't know about objects, a very, very verboten way to do this is simply to use global variables in your procedure. (shudder)

it counts as an action proc now, because it dosen't take parameters... but all other coders will hate you. just putting that out there.

-----------------------------------
joedirt
Sun Dec 17, 2006 7:19 pm


-----------------------------------
hey freakman,

I'm a fairly new to turing a i was woundering if you could explain or make a code for a simple custon button. One where all you do is click and there is no rollover of whatever, just as simple as you can.

-thanks

-----------------------------------
Clayton
Sun Dec 17, 2006 7:30 pm


-----------------------------------

var button1 : int := Buttons.Create (30, 30, "Hello")
loop
    if Buttons.Pressed (button1) then
        Draw.FillBox (maxx div 2, maxy div 2, maxx, maxy, brightred)
        Time.Delay(100)
    end if
end loop


Read my above posts and it well tell you all you need to know to understand.

-----------------------------------
joedirt
Sun Dec 17, 2006 8:03 pm


-----------------------------------
i got this far but i still can't figure out how to make a if statment which only works when the mouse is over a button and the mouse is clicked at the same time. in this code i'll click away from the button then move my mouse over it and it still works. Please help.



var x, y, button : int
procedure redbox
    Draw.FillBox (10, 10, 50, 50, red)
end redbox
procedure box
    Draw.FillBox (10, 10, 50, 50, black)
end box


loop
    Mouse.Where (x, y, button)
    box
    var x1, y1 : int
    if x < 50 and y < 50 and Mouse.ButtonMoved ("down") then

        redbox
        delay (1000)
    end if
end loop

-----------------------------------
ericfourfour
Sun Dec 17, 2006 11:28 pm


-----------------------------------
I have two ideas to handle the actions. The first one is to use an action class. The second is to inherit the button object and use it.
