Closed Dassadar closed 7 years ago
Hello, There is hover and leave functions on slots, you can use them to achieve some rudimentary tooltip behavior :
Shoes.app width: 300, height: 200 do
area = stack width: 50, height: 50, top: 40, left: 40 do
background darkorange..lime
end
area.hover do |slot|
slot.contents[0].fill = lime..darkorange
slot.refresh_slot
@popup = window width: 100, height: 50, title: "Tooltip" do
inscription "Time has no respect for whatever is done without itself !"
end unless @popup
end
area.leave do |slot|
slot.contents[0].fill = darkorange..lime
slot.refresh_slot
@popup.close if @popup
@popup = nil
end
end
So yes there is mouse over functionalities, no nothing planned at the moment for a real tooltip/popup behavior ... Regards
We don't really have a light weight tool tip. Perhaps a tooltip => string style could be added to stacks, flows and all widgets and cursor handling could be modified detect that but that would be a very large change and very difficult to debug for all platforms. We don't have menus and menu items for similar reasons - both are unbelievably complicated and would change Shoes from being fun and easy.
Request noted. Don't hold your breath waiting.
Hello everyone !
I am working with shoes for some time and really wanted to contribute! I am not a developer so I am not sure if my tips will come in handy but still here they are!
I am currently writing a small app which has 140 elements, each of which require description pop up which had to be presented next to the mouse pointer.
I tried a few things (most of them unsuccessful). At the end I reached the desired behavior with the code. There is a hidden flow/stack named @menu. It appears where the mouse pointer is!
X and Y define the current element in array of arrays called @box, each box is 40x40 slot and may have image inside. The problem is that when empty no action should happen during interaction. When there is image there is also a hidden semi transparent bright hidden colour defined for the slot which brights the image when hovered (defined as array of arrays named @select ).
Another problem I stumbled upon is how to dynamically configure the the width and height of the @menu so the text fits. I figured out this by placing a formula which reconfigure the @menu parameters based on the length of the text (@description array).
At the end there is the toggle action which shows the pop up !
motion do |L, T|
@move_left=L
@move_top=T
end
def skillz x, y
@box[x][y].hover do
unless @select[x][y]==nil then
@menu.style width: 210+text.length/3, height: 90+(text.length/7)*3
menu_left=@move_left; menu_top=@move_top;
if @move_left+@menu.width >= app.width+10 then menu_left=@move_left-@menu.width end
if @move_top+@menu.height >= app.height+10 then menu_top=@move_top-@menu.height end
@menu.move(menu_left, menu_top)
@select[x][y].toggle;
@menu.clear do
background rgb(80,100,120,0.8)
border(ghostwhite, strokewidth: 2)
para "#{@description[x][y]}", justify: true, align: "center", stroke: white
end
timer(0.1) { @menu.toggle }
end
end
@box[x][y].leave { unless @select[x][y]==nil then @select[x][y].toggle; @menu.toggle end }
end
I hope this is helpful!
hello dredknight, Welcome Thank you very much!
Nice idea, Could you provide a little shoes app demonstrating the basic idea of your pop up, for example just one or two boxes with the hover/leave mechanism and of course all the minimum setting the demo might need
for the code markdown : type this (without the quotes) " ```ruby " the line before your code and then the same without the word "ruby" but the line after your code for details look up here : https://guides.github.com/features/mastering-markdown/
Done! Thanks a lot!
Give me a week I will provide you the complete scenario!
P.S.
Actually it has all the things we discuss as it is now so check it out from here -> https://www.dropbox.com/s/7waei93xuctnwko/beta_skillwheel.exe?dl=0
@dredknight - Welcome, We can always use help. A lot of Shoes is written in Ruby. For this particular example, We could put up snippets/tips/sample category in the wiki and you can put/edit you example as an article. Getting a new article linked into wiki menus is a minor pain and uploading images to the wiki is obscure. That way your work won't be lost in a bug report.
Thanks a lot! I I am quite new to git hub so I am not really aware where should I go and write the article. I saw that I can add new page on the wiki. Is this how it is made?
@dredknight , Correct. Pick a faiirly short title and edit away in markdown format. Save button is on the bottom. The let me know on this message what the title is and I'll setup the menu for that. (You could do that yourself too but that's a lot more effort for some just starting out. You can alway delete it you don't like and the title can be changed.
@dredknight - I took the liberty of moving your comment to https://github.com/Shoes3/shoes3/wiki/Tooltips-Fun which you can edit to your desire.
Great thanks! I will wait a bit before I edit it. I want to make the method crystal clear easy first.
This is fun, we should try all kind of approaches to this, shows the power of Shoes ... @Dassadar, Building upon @dredknight idea, i tested a few things : tooltip with shadows, a bit of thickness, transparency as @dredknight suggested, using start method to draw above all other elements, also an alternative to Shoes::Widget (as in Shoes.log or Shoes.manual)
tiny app is here : https://gist.github.com/passenger94/e1e5c86c9fbb7a225ccf
Very nice!
I am not very good at ruby yet there is one thing I do not understand in the code
pop_slot.instance_eval %{ ##### What instance_evail% means ? how does it work??
def slide(x,y)
move(x-#{@offset}, y-#{@offset}) ### how do you define what are you moving here?
self
end
def tip=(tp)
contents[3].text = tp ### where is contents[] array defined?
end
def back_color=(pattern)
contents[2].fill = pattern
refresh_slot #### what is this? I dont see it anywhere? how come it works?
end
}
P.S. Alright.. just as I wrote this I got a vision ! Basically I think it works like this - pop_slot.instance_eval % open up for edit the stack above. all the things I dont understand are actually predefined variables in the STACK method. anyway any explanation will be welcomed :) ireally dont get that % sign.
Hi, Yes that's it ! instance_eval is one of the numerous way in ruby to add dynamically methods to an object it takes either a string or a block as argument, the %{} is one of the numerous way in ruby to write a string :-D https://en.wikibooks.org/wiki/Ruby_Programming/Syntax/Literals instance_eval is creating methods in the context of the receiver, here pop_slot, so in there self is that object and all calls to method are ending up on the object, for example pop_slot.contents[3]
refresh_slot is a new method recently added to slots, maybe we forgot to write a manual entry ! (allows us to tell lazy Shoes to do it's work and draw immediately)
Alright... I need to experiment... see more examples of this. I hope I can create some other magic with it :D
One more thing:
At row 60 of the code there is this @pop.slide
This calls module pop_slot.slide but how??? they are different and @pop is never defined...
look in shoes.rb and the shoes ruby library, Tons of inspiration there :-)
Yes this is how I learned most of the stuff. Just one thing do not comprehend...
Row 60
@pop.slide(@mx, @my).
Basically it is not defined anywhere before that and on the top of that Shoes understsands that you call SLIDE definition from the class.
Here is how I see it though not sure if correct.
@pop is new variable with (@)self so basically it inherits the possibility to call class instances that are in the app by default (inheritance).
If it is not like this I dont get it...
Maybe there is 2 not so clear things going on here: first remember that instance variables are available everywhere in the Shoes app block no matter where you defined them (inside the block of course) there's a chapter which talks a bit about it in the Manual : Shoes >> Rules the second is just a matter of understanding the timing of events i could have written the stack and events callback like this (in pseudo code) :
main_slot = stack do
caption ...
@active_slot = flow ... do
background ...
para ...
end
edit_line ...
para ...
end
main_slot.start { @pop = pop_over.slide @mx-1000, @my-1000 }
@active_slot.hover { @pop.slide(@mx, @my) ... }
@active_slot.leave { ... }
the @pop object is defined in the start event callback, otherwise as Shoes drawings are asynchronous, the edit_line would be drawn over the @pop ! (that's why i put that edit_line in there, just as visual feedback) so :
and now we have our @pop objet alive with his new methods you know the rest of the story :-) hope it helps !
PS both way of writing the code are equivalent, just personal taste
Thanks!!! Back to work now when I have results I will share them :)
I took the liberty of playing with that code you gave me. If you change some of the class code with this thing here you get dynamically resizable pop up window. The the size depends on the length of the input string.
There are a lot of flows though. if you place a new line symbol in the string the height of the block would not be increased by one line. there is also a problem with big words (try to place a word of 30 symbols for example). the string is not transferred on the line below when it reaches the end of the width.
It is very basic but this is as far as I could go for now. Let me know if you have any tips for how I can detect tabulation and new line in the string. There was some issue with dynamically resizing the shadow as well so I just commented it.
Cheers!
pop_slot = stack width: @pwidth, height: 200 do
image width: @pwidth, height: 200 do
#rect @offset, @offset, (@pwidth-@offset*2), 100, curve: 15, fill: rgb(0,0,0,0.4), stroke: rgb(0,0,0,0)
#shadow radius: 10, distance: 10, fill: rgb(0,0,0,0.8)
end
background rgb(255,255,255,0.5)
background rgb(255,0,255,0.5)
para msg
end
pop_slot.instance_eval %{
def slide(x,y)
move(x-#{@offset}, y-#{@offset})
self
end
def tip=(tp)
contents[3].text = tp
counter = tp.length
size = 20+tp.length*10
height=60
while counter > 50 do
size=400
height+=25
counter-=50
end
contents[0].style width: size , height: 200;
#contents[0].rect.style left: #{@offset} ,top: #{@offset}, width: (size-#{@offset}),height: height
contents[1].style curve: 15, width: (size-#{@offset}*2-1), height: height, left: #{@offset}, top: #{@offset}
contents[2].style curve: 15, width: (size-#{@offset}*2-3), height: height, left: #{@offset}+2, top:#{@offset}+2
contents[3].style left: #{@offset}+10, top: #{@offset}+10, width: size-#{@offset}*2-10
end
def back_color=(pattern)
contents[2].fill = pattern
refresh_slot
end
}
pop_slot
Ha ha !! Cool ! :+1:
I'm wondering though if you wouldn't be better to recreate completely the slot on each new text instead of trying to accommodate because it's getting real hot in there :-D
For the shadow you would have to manage the size of it also and the @offset (shadow -all effects - needs room around them otherwise you get rendering artefacts)
for a big word there is no automatic cut and line return that i'm aware of in Shoes , so you probably have to manage this. for tabulations and newlines, i think you would probably have to use some regex, scan the string, increase size, etc ... another level of complexity ...(maybe there is a better solution) Looks like a candidate for an outside method, maybe an entire Class ! Also if you go for that level of complexity, maybe a module is not good anymore and a Shoes::Widget would be better ...
Meanwhile getting a clean way to do it, you could always cheat :notes: , and calculate beforehand the needed size, and feed the slot initializer with the good numbers, in most situation one provide hard coded text anyway so why not give dimensions ...
EDIT : one more thing, use a mono font for the text so you have (more ?) predictable text size.
passenger94 very good tips but there are some technical issues :D. I dont know how to create a widget.. never done such. Otherwise as soon as I feel more comfortable with classes I will refurbish it from top to bottom :).
i just wrote something about Shoes::Widget : https://github.com/Shoes3/shoes3/wiki/Of-Shoes::Widget-and-Shoes::Canvas
Thanks this shed quite some light! I think there is an error though.
The widget class is named CheckText but it is called with the name of check_text. Why they are not the same?
I need to make that clear, thanks ! check_text is a factory method : it creates an instance of CheckText object, the name of that method is build dynamically from the name of the Class, following established convention (not that much shared convention , have to admit ) : https://github.com/Shoes3/shoes3/blob/master/lib/shoes.rb#L613 takes the CamelCase name of the Class, downcase it and separate 'words" delimited by Capital letters with a "_" (don't remember the word now !!) A convenience method, so you can create the widget without much boilerplate.
i just wrote something about Shoes::Widget :
I think I've seen my faulty memory about when Shoes.app acts like a stack. It could have been multiple flows! I'm easily confused! I've linked the article into the wiki menus.
It could have been multiple flows!
it traps me almost every time :-D
btw, i'm trying to collect some info about self and app not beeing the same if you want to add notes ... https://gist.github.com/passenger94/833d309b8599b1e14353#file-canvas_app_and_self-md
Alright guys I started doing something of a widget but kind of dont understand why is not working...
class PopUp < Shoes::Widget
def initialize(element, text)
@element=element
@text=text
@offset=10
motion { |x,y| @mx = x; @my = y }
@pop_slot = stack width: 100, height: 100 do end.hide
end
def create
@element.hover do
debug( "show" )
@pop_slot.style width: header.length*10, height: header.length
pop_left=@mx;
pop_top=@my;
if @mx+@pop_slot.width >= app.width+@offset then pop_left=@mx-@pop_slot.width end
if @my+@pop_slot.height >= app.height+@offset then pop_top=@my-@pop_slot.height end
@pop_slot.move(pop_left,pop_top)
background rgb(248,248,255,0.8), curve: 15
background rgb(80,100,120,0.8), curve: 15, width: @pop_slot.width-4, height: @pop_slot.height-4, left: 2, top: 2
para "#{@text}", justify: true, align: "center", stroke: white, size: 10, width: @pop_slot.width-2*@offset, left: @offset, font: "Courier"
@pop_slot.toggle
end
@element.leave {@pop_slot.toggle; debug( "close" )}
end
end
Shoes.app width: 650, height: 200 do
@h_object = flow width: 200, height:200 do
background green
end
@description="tool"
pop_up.new(@h_object, "#{@description}").create
Shoes.show_log
end
I get wrong number of arguments (0 of 2) error... I bet it is something really stupid... What I did wrong?
pop_up.new is the faulty
you don't call new on a Widget object to initialize it, you call directly the convenience method pop_up
It's a factory method ! so pop_up(@h_object, "#{@description}")
will do. (you can chain with create of course)
you might have another problem later, what is header ? probably just a part of code you don't show here ?
Thanks ! I took this from another code and reshaped it just for the purpose of the popup widget.
Header is actually the length of the imported text. Check appropriate changes below! The @text.length formula is not correct still, I want to make it work first, later I will adjust the size in conjunction with the monotype font. Thanks a lot for the tips! :)
class PopUp < Shoes::Widget
def initialize(element, text)
@element=element
@text=text
@offset=10
motion { |x,y| @mx = x; @my = y }
@pop_slot = stack width: 100, height: 100 do end.hide
end
def create
@element.hover do
debug( "show" )
@pop_slot.style width: @text.length*10, height: @text.length
pop_left=@mx;
pop_top=@my;
if @mx+@pop_slot.width >= app.width+@offset then pop_left=@mx-@pop_slot.width end
if @my+@pop_slot.height >= app.height+@offset then pop_top=@my-@pop_slot.height end
@pop_slot.move(pop_left,pop_top)
background rgb(248,248,255,0.8), curve: 15
background rgb(80,100,120,0.8), curve: 15, width: @pop_slot.width-4, height: @pop_slot.height-4, left: 2, top: 2
para "#{@text}", justify: true, align: "center", stroke: white, size: 10, width: @pop_slot.width-2*@offset, left: @offset, font: "Courier"
@pop_slot.toggle
end
@element.leave {@pop_slot.toggle; debug( "close" )}
end
end
Shoes.app width: 650, height: 200 do
@h_object = flow width: 200, height:200 do
background green
end
@description="tool"
pop_up(@h_object, "#{@description}").create
Shoes.show_log
end
Good news! I think it is going to work very nice :).
I just cannot understand one thing - I try to create a new stack/flow every time a pop up appears. Unfortunately it does not seem to be removed when I use finish or remove commands.
Here is a sample code from the one above.
class PopUp < Shoes::Widget
def initialize(element, text)
@element=element
@text=text
@offset=10
motion { |x,y| @mx = x; @my = y }
end
def create
@element.hover do
@pop_slot = stack do end.start
debug( "show" )
@pop_slot.style width: @text.length*10, height: @text.length
pop_left=@mx;
pop_top=@my;
if @mx+@pop_slot.width >= app.width+@offset then pop_left=@mx-@pop_slot.width end
if @my+@pop_slot.height >= app.height+@offset then pop_top=@my-@pop_slot.height end
@pop_slot.move(pop_left,pop_top)
background rgb(248,248,255,0.8), curve: 15
background rgb(80,100,120,0.8), curve: 15, width: @pop_slot.width-4, height: @pop_slot.height-4, left: 2, top: 2
para "#{@text}", justify: true, align: "center", stroke: white, size: 10, width: @pop_slot.width-2*@offset, left: @offset, font: "Courier"
@pop_slot.toggle
end
@element.leave {@pop_slot.finish; debug( "close" )}
end
end
Shoes.app width: 600, height: 600 do
@h_object = flow width: 200, height:200 do
background green
end
@description="tool"
pop_up(@h_object, "#{@description}").create
Shoes.show_log
end
Great ! some remarks : All events related methods, like start and finish, expect a block as parameter, they are not meant to be used alone, you must provide a block, inside that block you describe what you want to happen when Shoes fires that event. So for example @pop_slot.finish won't do anything because you didn't tell Shoes what to do at the finish event of @pop_slot (that event happens when @pop_slot is removed). Likewise for start event, it's not a command to start @pop_slot, there Shoes expects you to tell it what to do when start event happens (i.e. when the slot is drawn). In short do as you do with hover and leave.
You have to put what will be inside your pop_up (backgrounds; para) into @pop_slot or append them later
do you need the create method ? otherwise you can make everything happening in initialize
one can also initialize the slot, move it outside visible area and @pop_slot.clear { rebuild the inners of the pop_up }
and bring it back at hover event.
Not sure to understand : do you want your pop_up to appear above the element (green area here) or below like it is in your example ?
adapting (one possible way) your code, this works for me :
class PopUp < Shoes::Widget
def initialize(element, text)
@element=element
@text=text
@width = @text.length*12
@height = @text.length * 8
@offset=10
motion { |x,y| @mx = x; @my = y }
end
def create
@element.hover do
@pop_slot = stack left: 0, top: 0, width: @width, height: @height do
background rgb(248,248,255,0.8), curve: 15
background rgb(80,100,120,0.8), curve: 15, width: @width-4, height: @height-4, left: 2, top: 2
para "#{@text}", justify: true, align: "center", stroke: white, size: 10, width: @width-2*@offset, left: @offset, font: "Courier"
end
debug( "show" )
pop_left=@mx;
pop_top=@my;
if @mx+@width >= app.width+@offset then pop_left=@mx-@width end
if @my+@height >= app.height+@offset then pop_top=@my-@height end
@pop_slot.move(pop_left,pop_top)
end
@element.leave {@pop_slot.remove; debug( "close" )}
end
end
Shoes.app width: 600, height: 600 do
@h_object = flow width: 200, height:200 do
background green
end
@description="tool"
pop_up(@h_object, "#{@description}").create
Shoes.show_log
end
hahaha thanks @passenger94 :) The funny thing is that I have totally misunderstood start and finish. I believed that they start and finish the event , i.e. stack.finish will do the same as stack.remove, but it was the other way around... when one task finishes (in the current example the stack) start a another one (the one in brackets). Point taken!
I always want the popup to be on top on all stacks but I assumed this will be the case by default because when I call the class instance it creates slot and the latest created slot is always on top.
check this one now. With our mutual effort now the text box is recalibrating perfectly based on text :) It also works for multiple elements.
class PopUp < Shoes::Widget
def initialize(element, text)
@element=element
@text=text
@width = @text.length*10
@height = 50
while @width >250 do
case @width
when 501..Float::INFINITY then @height+=21; @width-=250
when 250..500 then @height+=21; @width=250
end
end
@offset=10
motion { |x,y| @mx = x; @my = y }
end
def create
@element.hover do
@pop_slot = stack left: 0, top: 0, width: @width, height: @height do
background rgb(248,248,255,0.8), curve: 15
background rgb(80,100,120,0.8), curve: 15, width: @width-4, height: @height-4, left: 2, top: 2
para "#{@text}", justify: true, align: "center", stroke: white, size: 10, width: @width-2*@offset, left: @offset, font: "Courier"
end
debug( "show" )
pop_left=@mx;
pop_top=@my;
if @mx+@width >= app.width+@offset then pop_left=@mx-@width end
if @my+@height >= app.height+@offset then pop_top=@my-@height end
@pop_slot.move(pop_left,pop_top)
# @pop_slot.toggle
end
@element.leave {@pop_slot.remove; debug( "close" )}
end
end
Shoes.app width: 600, height: 600 do
@h_object = flow width: 200, height:200 do
background green
end
@h_object2 = flow width: 200, height:200 do
background blue
end
@description="tool ddddddddddd dddddddddddddddddddddddd ddddddddddddv dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd ddddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd wwwwww dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd wwwwww dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd dddddd wwwwww dddddd dddddd dddddd qqqqqq wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww wwwwwwwwwwww weeeeeeeeweeeeeeeeweeeeeeee weeeeeeee "
@des="ABVADSDAS"
pop_up(@h_object, "#{@description}").create
pop_up(@h_object2, "#{@des}").create
Shoes.show_log
end
Just poppin in to say that I am still working on the popup. I am closing on to something I appreciate more and more. When I think it is ready I will share it here :)
Hey everyone,
I bumped into a technological issue while working on the popup thing. Lately I am very glad on how the methods are working so I decided to move to the next step and merge them into a little class.
The problem is purely connected to my inability to understand ruby classes.. I just cant get the hang out of this. Let me show you what happens exactly. I have this modules defined.
def set_popup img, wide, high, header, text = "", text2 = ""
img.hover { open_popup wide, high, header, text, text2; }
img.leave { close_popup }
motion { |x, y| @move_left = x; @move_top = y }
end
def open_popup wide, high, header = "", text = "", text2 = ""
offset = 10
@menu.style width: wide, height: high
menu_left = @move_left; menu_top = @move_top;
@move_left+@menu.width >= app.width+offset ? menu_left = @move_left-@menu.width : nil
@move_top+@menu.height >= app.height+offset ? menu_top = @move_top-@menu.height : nil
@menu.move(menu_left, menu_top)
@menu.clear do
background rgb(120,42,5,0.5), curve: 15
background rgb(180,150,110,0.7), curve: 15, width: @menu.width-4, height: @menu.height-4, left: 2, top: 2
header != "" ? ( tagline strong("#{header}"), stroke: white ) : nil
text != "" ? ( para "#{text}", justify: true, align: "center", stroke: white, size: 10, width: @menu.width-2*offset, left: offset ) : nil
text2 != "" ? (para "#{text2}", justify: true, align: "center", stroke: rgb(50,50,50), size: 10, width: @menu.width-2*offset, left: offset ) : nil
end
timer(0.1) { @menu.show }
end
def close_popup
@menu.hide
end
Basically when you have them you can define popup on pretty much everything with an ease. The only drawback that I see is that one have to define the @menu (the variable that hold the hidden window that represents the popup) in the main app so it can hover globally for the whole APP and not just the insides of specific stack/flow.
Here is an example.
Shoes.app do
Object1 = image "shoes.png"
Object2 = image "gloves.png"
set_popup ( Object1, 200, 50, "", "these are shoes" )
set_popup ( Object2, 200, 50, "", "these are gloves" )
@menu = stack height: 10 do end.hide
end
You can see how this operate in a complicated way by checking this app.
Now what I want to do is migrate this functionality in Class. I imagine this working the following way.
class popup
initialize
@menu = stack height: 10 do end.hide ##this stack should appear globally
end
def create (object, wide, high, text)
object.hover do
#settting text
# setup menu width/height
#menu appears
end
object.leave do
@menu.hide
end
end
Shoes.app do
pop1 = popup.new
some_image1 = "shoes.png"
some_image2 = "gloves.png"
popup.create some_image1, 100, 50, "these are shoes"
popup.create some_image2, 100, 50, "these are gloves"
end
Is it possible?
I dont know where to begin. I have basic understanding of class but it seems what class is, is not what i want while what I want is something classes offer - keeping/providing all variables in one instance.
P.S. I had some numerous tries on this and all of them ended nowhere...
Is it possible?
It probably is but be careful and understand where Shoes only Looks Like Ruby (tm) because it's very confusing to me too when I poke around that part of Shoes. There's information scattered about in the manual and wiki about some of what you want. @menu
is a class variable of what class? Are you sure? The answer might surprise you once you get ruby to tell you. You do need to learn how to do introspection in Ruby and understand how classes and module work before digging into what Shoes does to confuse things. Hint - read the doc for Object, Class and Module or a good book on Ruby.
If all the 'things' with a popup are of the same ' type' you might look at sub-classing Shoes::widget. Look at c:"Program Files (x86)\shoes\samples\expert-custom-list-box.rb to get a feel for what can be done. It might be the best choice or maybe not. It deserves the 'expert' rating because it's expert territory and I'm in that group of Ruby campers.
agree ! looks like a good candidate for a Shoes::Widget otherwise you need a reference to the app object inside the class (at initialize) and explicitly call shoes methods on it, like stack : ( a Ruby class knows nothing about Shoes!)
class Popup
def initialize(theapp)
@app = theapp
@menu = @app.stack
end
end
you create your class like this
Shoes.app do
Popup.new(self)
end
but Shoes::Widget might be easier
@passenger94 I just started reading your Widget article. It explains not only widgets but how the whole Shoes thing work. It really sounds quite manageable now! There is one issue I foresee but I will get back to you if it really appears :).
Cheers!
Alright I bumped into the issue and it seems even harder than I thought. Actually I think the way Shoes works will not allow this widget to happen. First here is the code
class PopUp < Shoes::Widget
def initialize(img, text, options={} )
motion { |x, y| @move_left = x; @move_top = y };
@img = img
@text = text
@wide = options[:width] || 310
@high = options[:height] || 70+(5*(text.length+text2.length))/10
@header = options[:hdr] || ""
@text2 = options[:txt2] || ""
@img.hover { open_popup }
@img.leave { close_popup }
end
def open_popup
motion { |x, y| @move_left = x; @move_top = y };
debug ("app width is #{app.width}, move left is #{@move_left}")
offset = 10
self.style width: @wide, height: @high
menu_left = @move_left; menu_top = @move_top;
@move_left+self.width >= app.width+offset ? menu_left = @move_left-self.width : nil
@move_top+self.height >= app.height+offset ? menu_top = @move_top-self.height : nil
self.move(menu_left, menu_top)
self.clear do
background rgb(120,42,5,0.5), curve: 15
background rgb(180,150,110,0.7), curve: 15, width: self.width-4, height: self.height-4, left: 2, top: 2
@header != "" ? ( tagline strong("#{@header}"), stroke: white ) : nil
@text != "" ? ( para "#{@text}", justify: true, align: "center", stroke: white, size: 10, width: self.width-2*offset, left: offset ) : nil
@text2 != "" ? (para "#{@text2}", justify: true, align: "center", stroke: rgb(50,50,50), size: 10, width: self.width-2*offset, left: offset ) : nil
end
timer(0.1) { self.show }
end
def close_popup
self.hide
end
end
Here it is how you call it
pop_up object, "some text", width: 240, height: 50
Unfortunately if the object is nested in at least one layer of slots this automatically assign that slot as owner of the widget (self is the slot). This is not ok because the popup window is trimmed by the borders of the slot and cannot roam freely.
My question is how to make the whole Canvas always to be the owner of the popup slot nevertheless where the object/image that calls the popup is nested?
The same root issue also causes problems with motion detection of x and y.
if i understand correctly, remember a widget is ultimately just a slot, so it's inserted in the context where you initialize it unless you explicitly told it not to do so.
i think you want to initialize your widget at the app level when all other objects are drawn (to make sure it's the last one hence above the others) - maybe create a routine that redraw your widget to be always the last one, if necessary -. At initialisation don't give it specific object nor define hover/leave events. Create a function called, say "plug", give it, as parameter, an object you want to react to said events, (with other needed params) possibly maintain an array of all "plugged" objects, define the hover/leave events for the object
Not tested, not sure, just what's comes to mind ...
Alright your idea brighten me up for sure :). Here is my tryout I hope I understood you correctly.
Unfortunately it is not working as expected. What I do is the following. No stack/flow defined, I will use the widget slot. initialize is empty. I initialize the widget at the end of the code so it can go on top of everything. Inside the code I use pop_up.set function to define the thingies and move the pop up slot wherever it is required. Unfortunately it is still NOT on top of the other slots and it is trimmed :(.
Here is an example
class PopUp < Shoes::Widget
def initialize
motion { |x, y| @move_left = x; @move_top = y }
end
def set (img, text, options={} )
@img = img
@text = text
@wide = options[:width] || 310
@high = options[:height] || 70+(5*(text.length+text2.length))/10
@header = options[:hdr] || ""
@text2 = options[:txt2] || ""
@img.hover { open_popup }
@img.leave { close_popup }
end
def open_popup
debug ("app width is #{app.width}, move left is #{@move_left}")
offset = 10
self.style width: @wide, height: @high
menu_left = @move_left; menu_top = @move_top;
@move_left+self.width >= app.width+offset ? menu_left = @move_left-self.width : nil
@move_top+self.height >= app.height+offset ? menu_top = @move_top-self.height : nil
self.move(menu_left, menu_top)
self.clear do
background rgb(120,42,5,0.5), curve: 15
background rgb(180,150,110,0.7), curve: 15, width: self.width-4, height: self.height-4, left: 2, top: 2
@header != "" ? ( tagline strong("#{@header}"), stroke: white ) : nil
@text != "" ? ( para "#{@text}", justify: true, align: "center", stroke: white, size: 10, width: self.width-2*offset, left: offset ) : nil
@text2 != "" ? (para "#{@text2}", justify: true, align: "center", stroke: rgb(50,50,50), size: 10, width: self.width-2*offset, left: offset ) : nil
end
timer(0.1) { self.show }
end
def close_popup
self.hide
end
end
Shoes.app do
stack do
flow width: 40, height: 40 do
img1 = image "shoes.png
pop_up.set (img1, text, options)
end
end
pop_up #### here I define it so it can be on top of the slots
end
Unfortunately it is still trimmed. How do I redraw objects so they can remain on top? This is what I want to try next.
ooops i forgot ...
drawings in Shoes are asynchronous, that essentially means that there is no guarantee about when the drawings are happening, fortunately there is the start event on slots to deal with this. (start event is fired when the slot is drawn) .
so for the root slot, try this
Shoes.app do
stack do
flow width: 40, height: 40 do
img1 = image "shoes.png
pop_up.set (img1, text, options)
end
end
start { pop_up } # or app.slot.start { pop_up }
end
start { pop_up } - undefined method is missing. Why is that? everything is defined. I also get this error when calling...
pop_up.set bla bla
...from a method called within another method.
Are there any restrictions when it comes to nesting?
i didn't look into your code ... you can't call pop_up.set before creating pop_up widget !!
create all your objects, than in start event callback call pop_up to actually create the widget and assign a variable to it like @menu = pop_up Then call set like @menu.set(img1, text, options)
Alright! so popup definition should be done once when the whole code is written. Like this..
define pop widget
Shoes.app do
create the slots on the canvas
create objects in the slots, all objects that are going to be hovered will be assigned as @hover_box array elements.
initialize @menu = pop_up wdiget
Now when the whole app is written launch a cycle and rotate through all the objects and define them hover -> @menu.set @hover_box[@]
end
This will work, though I hoped that one will be able to define the popups inbetween the code like this.
define pop widget
Shoes.app do
Initialize @menu = pop_up
create the left slot -> Rotate through 10 elements to create them and while creating them assign popups (@menu.set)
create the middle slot -> Rotate through 160 elements to create them and while creating them assign popups (@menu.set)
create the right slot -> create 2 elements and assign popup on the go (@menu.set)
end
May be I am jinxed or something but the first way seems counter-intuitive to me. Unless there is some way to tell a slot " You will always be my favourite. Please stay always on top" I will go with it.
Anyway the good thing is that finally I got your idea ;).
Shoes was designed for doing easy things, easily. When you go beyond that simplicity (and you are, as have many before you) then Shoes stops becoming easy and gets demanding that you do things it's way. Can you do it your way? Probably if your willing to exert great effort .Will it be worth fighting Shoes to do it your way? Most people have hit the wall will quit Shoes or abandon their project or they adapt to what work with Shoes can do.
It's a philosophical thing. There's too much low level code inside Shoes to change things now, and too few people maintaining it to for any meaningful change in the design. It's not a good thing or a bad thing. It's just Shoes.
Good :), I will do it the way meant by the force of shoes then!
Props to @passenger94 for patiently explaining everything that i asked.
Hey guys here are two versions you can consider PoC.
Version A: Uses an external menu_slot for the actual popup. The restrictions are that the widget should use a slot which is defined last, so it can appear on top of other slots. This means that the widget will be initialized last in the code just after the menu_slot is created.
class PopUp < Shoes::Widget
def initialize ( menu )
motion { |x, y| @move_left = x; @move_top = y }
@menu = menu
end
def resize (wide, high, length )
while (((high*wide/20)/length) < 8 or wide/high > 6) do
case wide/high
when 0..6 then high+=20
else high+=20; wide-=wide/2
end
wide < 250 ? wide = 250 : nil
end
return wide, high + 10
end
def present ( text, size, font = "", stroke, just, offset, wide, high)
unless text == "" then
flow margin_left: offset, width: wide, height: high do
para ("#{text}"), size: size, stroke: stroke, font: font, align: "center", justify: just
end
end
end
def open ( text, options={} )
offset = 10
text2 = options[:text2] || ""
header = options[:header] || ""
wide = options[:width] || text.length*8
t_high = 20
(options[:width].is_a? Integer) == false ? (wide, t_high = resize wide, t_high, text.length) : nil
text2 == "" ? t2_high = 0 : t2_high = 8*20*text2.length/wide + 20
header != "" ? h_high = 60 : h_high = 0
high = options[:height] || t_high + h_high + t2_high
@menu.style width: offset + wide, height: high + offset
menu_left = @move_left; menu_top = @move_top;
@move_left + @menu.width >= app.width + offset ? menu_left = @move_left - @menu.width : nil
@move_top + @menu.height >= app.height + offset ? menu_top = @move_top - 2*@menu.height/3 : nil
@menu.move(menu_left, menu_top)
@menu.clear do
background rgb(120,42,5,0.5), curve: 15
background rgb(180,150,110,0.7), curve: 15, width: @menu.width-4, height: @menu.height - 4, left: 2, top: 2
present header, 17, "Bell MT", white, false, offset, wide, h_high
present text, 9, "Courier New", white, true, offset/2, wide, t_high
present text2, 9, "Courier New", rgb(50,50,50), true, offset/2, wide, t2_high
end
timer(0.1) { @menu.show }
end
def close
@menu.hide
end
end
Shoes.app do
flow height: 100, width: 100 do
border("rgb(105, 105, 105)", strokewidth: 1)
image "shoes-icon.png", left: 1, top: 1, width: 50, height: 50
contents[1].hover { @pops.open "morning, sunshine, cofee, beans, qeqwe ,asdasds ,wqeqwewqe 22222222222 222222222222222 2222222222222 2222 22222222222222222" }
contents[1].leave { @pops.close }
end
flow height: 100, width: 100 do
border("rgb(105, 105, 105)", strokewidth: 1)
image "shoes-icon.png", left: 1, top: 1, width: 50, height: 50
contents[1].hover { @pops.open "morning, sunshine, cofee, beans, qeqwe ,asdasds ,wqeqwewqe", text2: "Lets get it on" }
contents[1].leave { @pops.close }
end
flow height: 100, width: 100 do
border("rgb(105, 105, 105)", strokewidth: 1)
image "shoes-icon.png", left: 1, top: 1, width: 50, height: 50
contents[1].hover { @pops.open "morning, sunshine, cofee, beans, qeqwe ,asdasds ,wqeqwewqe", text2: "Letsad dad getsdaf it on1231d", header: "Header!" }
contents[1].leave { @pops.close }
end
@menu = stack height: 10 do end.hide
@pops = pop_up ( @menu )
end
Version B: I found a way to override shoes order of elements by using timer. When the widget is initialized its wait X seconds so the whole code is executed and then it creates the menu slot. This looks far better than the one above as it can be initialized everywhere! The only problem is that we dont know if the code will manage to be executed for X seconds so timer is not very reliable if the code one is using is very complicated. Also multiple timers in the code will make this completely useless. Also the first X seconds the popup is not available for obvious reasons.
I also tried creating a slot on the canvas like this:
app.slot.elements[99] = stack do end.hide
The idea is to create the last element so it is always on top. Unfortunately Shoes does not like this code :(.
Anyway here is a sample code of version B.
class PopUp < Shoes::Widget
def initialize ()
motion { |x, y| @move_left = x; @move_top = y }
timer(2) { @menu = stack height: 0 do end.hide }
end
def resize (wide, high, length )
while (((high*wide/20)/length) < 8 or wide/high > 6) do
case wide/high
when 0..6 then high+=20
else high+=20; wide-=wide/2
end
wide < 250 ? wide = 250 : nil
end
return wide, high + 10
end
def present ( text, size, font = "", stroke, just, offset, wide, high)
unless text == "" then
flow margin_left: offset, width: wide, height: high do
para ("#{text}"), size: size, stroke: stroke, font: font, align: "center", justify: just
end
end
end
def open ( text, options={} )
offset = 10
text2 = options[:text2] || ""
header = options[:header] || ""
wide = options[:width] || text.length*8
t_high = 20
(options[:width].is_a? Integer) == false ? (wide, t_high = resize wide, t_high, text.length) : nil
text2 == "" ? t2_high = 0 : t2_high = 8*20*text2.length/wide + 20
header != "" ? h_high = 60 : h_high = 0
high = options[:height] || t_high + h_high + t2_high
@menu.style width: offset + wide, height: high + offset
menu_left = @move_left; menu_top = @move_top;
@move_left + @menu.width >= app.width + offset ? menu_left = @move_left - @menu.width : nil
@move_top + @menu.height >= app.height + offset ? menu_top = @move_top - 2*@menu.height/3 : nil
@menu.move(menu_left, menu_top)
@menu.clear do
background rgb(120,42,5,0.5), curve: 15
background rgb(180,150,110,0.7), curve: 15, width: @menu.width-4, height: @menu.height - 4, left: 2, top: 2
present header, 17, "Bell MT", white, false, offset, wide, h_high
present text, 9, "Courier New", white, true, offset/2, wide, t_high
present text2, 9, "Courier New", rgb(50,50,50), true, offset/2, wide, t2_high
end
timer(0.1) { @menu.show }
end
def close
@menu.hide
end
end
Shoes.app do
@pops = pop_up()
flow height: 100, width: 100 do
border("rgb(105, 105, 105)", strokewidth: 1)
image "shoes-icon.png", left: 1, top: 1, width: 50, height: 50
contents[1].hover { @pops.open "morning, sunshine, cofee, beans, qeqwe ,asdasds ,wqeqwewqe 22222222222 222222222222222 2222222222222 2222 22222222222222222" }
contents[1].leave { @pops.close }
end
flow height: 100, width: 100 do
border("rgb(105, 105, 105)", strokewidth: 1)
image "shoes-icon.png", left: 1, top: 1, width: 50, height: 50
contents[1].hover { @pops.open "morning, sunshine, cofee, beans, qeqwe ,asdasds ,wqeqwewqe", text2: "Lets get it on" }
contents[1].leave { @pops.close }
end
flow height: 100, width: 100 do
border("rgb(105, 105, 105)", strokewidth: 1)
image "shoes-icon.png", left: 1, top: 1, width: 50, height: 50
contents[1].hover { @pops.open "morning, sunshine, cofee, beans, qeqwe ,asdasds ,wqeqwewqe", text2: "Letsad dad getsdaf it on1231d", header: "Header!" }
contents[1].leave { @pops.close }
end
end
How the Widget works.
User is obliged to enter 1 variable of type string only.
@pops.open "some text"
User can add second descriptive text if he wishes. Text is below the first one and in different colour.
@pops.open "some text", text2: "descriptive text"
A header can be added that appears on top.
@pops.open "some text", text2: "descriptive text" , header: "Heya"
The widget resizes the menu based on the size of the text. This is valid for all 3 boxes - text, text2 and header!
if you dont like how the widget resizes the box you can override it by entering own values like this.
@pops.open "some text", text2: "descriptive text" , header: "Heya", width: 300, height: 400
@dredknight, did you tried the start event ? It's exactly doing internally what you are trying to do on B but in the flow of Shoes events, so it waits the necessary time for elements to be ready !
@passenger94 exchanged timer with start . Works wonderfully!!! Thank you!
So this is what start do? Waits for everything and then gets executed.
Is it possible to put a couple of starts in the code? if you do that in what order are they executed?
p.s. The following things break the dynamic arrangement of the slot.
Any ideas are welcome :).
Hello,
For shoes evolutions, is it planned to have a mouse over functionality compatible with stacks and flows please? For example to display a tooltip (so that would actually be two requirements! ;-) )
Regards, David