Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swt doesn't allow transparent backgrounds #79

Closed
wasnotrice opened this issue Jun 21, 2012 · 40 comments
Closed

Swt doesn't allow transparent backgrounds #79

wasnotrice opened this issue Jun 21, 2012 · 40 comments
Labels

Comments

@wasnotrice
Copy link
Member

I have been working on solving #move in #59 (see code at #78) and ran into this problem. Swt doesn't allow transparent backgrounds. I have been looking into this for a few days now, and can't seem to figure it out. So here are questions for you all:

  1. Can we implement Shoes in a framework that doesn't provide transparent backgrounds?
  2. If so, how?
  3. What am I missing?
@ashbb
Copy link
Member

ashbb commented Jun 22, 2012

@wasnotrice Sorry, I've not dived into #59 (also not reviewed your code at #78) yet. But, how about Composites with SWT::TRANSPARENT?
Try out the following snippet. It works like this on my Windows 7.

require 'java'
require 'swt'

$display = display = Swt::Widgets::Display.new
white = display.getSystemColor(Swt::SWT::COLOR_WHITE)
red = display.getSystemColor(Swt::SWT::COLOR_RED)
green = display.getSystemColor(Swt::SWT::COLOR_GREEN)

shell = Swt::Widgets::Shell.new display, Swt::SWT::SHELL_TRIM
shell.setBackground white
shell.addListener Swt::SWT::Close, proc{Swt.display.dispose}

cs1 = Swt::Widgets::Composite.new shell, Swt::SWT::TRANSPARENT
cs1.setSize 200, 200

blk = proc do |e|
  gc = e.gc
  gc.setBackground green
  gc.fillOval 25, 25, 100, 100
end
cs1.add_paint_listener(blk)

cs2 = Swt::Widgets::Composite.new cs1, Swt::SWT::TRANSPARENT
cs2.setSize 150, 150
cs2.setLocation 50, 50

blk = proc do |e|
  gc = e.gc
  gc.setBackground red
  gc.fillOval 25, 25, 100, 100
end
cs2.add_paint_listener(blk)

b = Swt::Widgets::Button.new cs1, Swt::SWT::PUSH
b.setText 'button'
b.pack

class Anim
  def initialize &blk
    @blk = blk
  end
  def run
    @blk.call
    $display.timerExec 100, self
  end
end
i = 0
a = Anim.new{i+=1; b.setLocation i, i; cs1.redraw}
display.timerExec 100, a

shell.pack
shell.open

Swt.event_loop{Swt.display.isDisposed}

@wasnotrice
Copy link
Member Author

I haven't really looked into SWT::TRANSPARENT because of this line in the docs:

Since:
3.4 WARNING: THIS API IS UNDER CONSTRUCTION AND SHOULD NOT BE USED

And it does seem unreliable. Here's how your snippet looks on OS X :(

@ashbb
Copy link
Member

ashbb commented Jun 22, 2012

Ahhh,... sad. :(
Can we choose SWT::NO_BACKGROUND or SWT::TRANSPARENT for each platforms?

@wasnotrice
Copy link
Member Author

Hmmm. That might work. I haven't tried SWT::TRANSPARENT on Linux. But this kind of defeats the purpose of using a cross-platform framework :(

@ashbb
Copy link
Member

ashbb commented Jun 23, 2012

Yeah, but there is no perfect cross-platform framework. So, if SWT::TRANSPARENT works on Linux, I think that is a acceptable workaround for now.

Okay now, back to your first question. We have to make a decision on which way to go.

  1. Stop using SWT. Choose other framework that provide transparent backgrounds.
  2. Switch to SWT::NO_BACKGROUND or SWT::TRANSPARENT for each platforms.
  3. Implement Shoes::Flow and Shoes::Stack without SWT::Composite like Purple Shoes.

Note: In the last case, it's difficult to add scrollbars for each Flow/Stack areas.

@jrgifford
Copy link
Member

On my Ubuntu box, @ashbb's snippet looks like this.

@wasnotrice
Copy link
Member Author

@jrgifford mine is similar except I get trails (this is ubuntu 11.04 in a Parallels VM). So TRANSPARENT doesn't work on OS X or on Linux.

Let's look at NO_BACKGROUND again:

By default, before a widget paints, the client area is filled with the current background. When this style is specified, the background is not filled, and the application is responsible for filling every pixel of the client area.

Can we work with this? What if we make sure that cs1 paints its whole area, like this?

require 'java'
require 'swt'

$display = display = Swt::Widgets::Display.new
white = display.getSystemColor(Swt::SWT::COLOR_WHITE)
red = display.getSystemColor(Swt::SWT::COLOR_RED)
green = display.getSystemColor(Swt::SWT::COLOR_GREEN)

shell = Swt::Widgets::Shell.new display, Swt::SWT::SHELL_TRIM
shell.setBackground white
shell.addListener Swt::SWT::Close, proc{Swt.display.dispose}

#cs1 = Swt::Widgets::Composite.new shell, Swt::SWT::TRANSPARENT
cs1 = Swt::Widgets::Composite.new shell, Swt::SWT::NO_BACKGROUND
cs1.setSize 200, 200

blk = proc do |e|
  gc = e.gc
  gc.setBackground white
  gc.fill_rectangle 0, 0, 200, 200
  gc.setBackground green
  gc.fillOval 25, 25, 100, 100
end
cs1.add_paint_listener(blk)

cs2 = Swt::Widgets::Composite.new cs1, Swt::SWT::NO_BACKGROUND
cs2.setSize 150, 150
cs2.setLocation 50, 50

blk = proc do |e|
  gc = e.gc
  gc.setBackground red
  gc.fillOval 25, 25, 100, 100
end
cs2.add_paint_listener(blk)

b = Swt::Widgets::Button.new cs1, Swt::SWT::PUSH
b.setText 'button'
b.pack

class Anim
  def initialize &blk
    @blk = blk
  end
  def run
    @blk.call
    $display.timerExec 100, self
  end
end

i = 0
a = Anim.new do
  i+=1
  b.setLocation i, i
  cs1.redraw
  shell.redraw(0, 0, shell.getSize.x, shell.getSize.y, true)
  shell.update
end
display.timerExec 100, a

shell.pack
shell.open

Swt.event_loop{Swt.display.isDisposed}

This removes the trails for me (but not the background of cs2). Also works on OS X. See this video.

@ashbb
Copy link
Member

ashbb commented Jun 26, 2012

@wasnotrice @jrgifford I just realized Swt::SWT::NO_BACKGROUND | Swt::SWT::TRANSPARENT works well, i.e. as same as this, on Windows 7. Could you confirm on your platforms?

@jrgifford
Copy link
Member

The snippet that @wasnotrice has above looks like this on 12.04. And I'm not quite sure what to change it to what @ashbb wants us to test. :P

@ashbb
Copy link
Member

ashbb commented Jun 26, 2012

@jrgifford Oh, sorry my poor explanation. xx-P

What I wanted to say is:

  • replace line 14 and 26 in the snippet that @wasnotrice has above like this.
cs1 = Swt::Widgets::Composite.new shell, Swt::SWT::NO_BACKGROUND | Swt::SWT::TRANSPARENT  # line 14
cs2 = Swt::Widgets::Composite.new cs1, Swt::SWT::NO_BACKGROUND | Swt::SWT::TRANSPARENT    # line 26

@wasnotrice
Copy link
Member Author

@ashbb When I use NO_BACKGROUND and TRANSPARENT, I don't notice any difference on OS X or linux from my previous version. Works well OS X. Works on Linux except that red circle has a gray background.

@wasnotrice
Copy link
Member Author

I found this article that uses Canvas with an image to get a transparent background. I wanted to experiment by changing our example from Composite to Canvas like this:

cs1 = Swt::Widgets::Canvas.new shell, Swt::SWT::NO_BACKGROUND | Swt::SWT::TRANSPARENT
# and later
cs2 = Swt::Widgets::Canvas.new cs1, Swt::SWT::NO_BACKGROUND | Swt::SWT::TRANSPARENT

But I get this error:

huckle:shoes-swing eric$ jruby --1.9 -J-XstartOnFirstThread ~/tmp/swt-transparent-2.rb 
NameError: uninitialized constant Swt::Widgets::Canvas
  const_missing at org/jruby/RubyModule.java:2642
         (root) at /Users/eric/tmp/swt-transparent-2.rb:14

What am I doing wrong?

@ashbb
Copy link
Member

ashbb commented Jun 27, 2012

@wasnotrice

First, thank you for the confirmaion.

When I use NO_BACKGROUND and TRANSPARENT,... Works well OS X.

Glad to hear that. Then we can get a composite container with transparent background without using RbConfig::CONFIG['host_os'] on Windows and OS X.

But,...

Works on Linux except theat red circle has a gray background.

Ah,... sad. :(
Isn't there anything that we can do for Linux?

Second, look at C:\jruby\lib\ruby\gems\1.8\gems\swt-0.12\lib\swt\full.rb.
Canvas is not imported. So, we need to add the following to use Canvas. ;-)

module Swt::Widgets
 import org.eclipse.swt.widgets.Canvas
end

After adding the above snippet, our example works with Canvas as same as Composite on my Windows 7.
I hope this becomes a solution for Linux...

@wasnotrice
Copy link
Member Author

@ashbb Thanks! But no luck so far on Linux :(

@ashbb
Copy link
Member

ashbb commented Jun 28, 2012

@wasnotrice and folks,

Well, it seems that there is no solution for Linux to make Swt::Composite with transparent background.

So, how about the following?

class ShoesComposite < Swt::Widgets::Composite
  def initialize *cs
    @cs = cs[0]
    super
  end

  def add_paint_listener blk
    @cs.add_paint_listener blk
  end
end

cs1 = Swt::Widgets::Composite.new shell, Swt::SWT::NULL  # implicit shoes top-slot
cs2 = ShoesComposite.new cs1, Swt::SWT::NULL             # Shoes Flow or Stack
  • ShoesComposite is a virtual container.
  • It is a sub-class of Swt::Composite, but paint elements on top-slot cs1 instead of on itself cs2.

This is the same story as Purple Shoes. Although the implementation is very different.

@wasnotrice
Copy link
Member Author

@ashbb I haven't played with this code yet, but do you think this allows us to do the layering that Shoes requires? By controlling the order of the paint callbacks, maybe?

@wasnotrice
Copy link
Member Author

@ashbb OK, now I've played with the code ;) But I can't get the button to slide in between the circles (it just passes over them). I made ShoesComposite not inherit from Swt::Composite because that I was getting errors about not being able to convert a ruby object into an Swt object.

Here's what I've got:

require 'java'
require 'swt'

module Swt
  include_package 'org.eclipse.swt.graphics'
  include_package 'org.eclipse.swt.events'
end

class ShoesComposite
  def initialize *cs
    @cs = cs[0]
  end

  def setSize(w,h)
    @width, @height = w, h
  end

  def setLocation(x,y)
    @x, @y = x, y
  end

  def add_paint_listener blk
    # Wrap the block so the paint callback happens at the right (x,y)   
    wrapped_blk = proc do |e|
      gc = e.gc
      transform = ::Swt::Transform.new(::Swt.display)
      transform.translate @x, @y
      gc.transform = transform
      blk.call(e)
      transform.dispose
    end
    @cs.add_paint_listener wrapped_blk
  end
end

$display = display = Swt::Widgets::Display.new
white = display.getSystemColor(Swt::SWT::COLOR_WHITE)
red = display.getSystemColor(Swt::SWT::COLOR_RED)
green = display.getSystemColor(Swt::SWT::COLOR_GREEN)

shell = Swt::Widgets::Shell.new display, Swt::SWT::SHELL_TRIM
shell.setBackground white
shell.addListener Swt::SWT::Close, proc{Swt.display.dispose}

# Implicit top slot
cs0 = Swt::Widgets::Composite.new shell, Swt::SWT::NULL
cs0.setSize 200, 200

# Slot 1 (green circle)
cs1 = ShoesComposite.new cs0
blk = proc do |e|
  gc = e.gc
  gc.setBackground white
  gc.fill_rectangle 0, 0, 200, 200
  gc.setBackground green
  gc.fillOval 25, 25, 100, 100
end
cs1.add_paint_listener(blk)

# Slot 2 (red circle)
cs2 = ShoesComposite.new cs0
cs2.setSize 150, 150
cs2.setLocation 50, 50
blk = proc do |e|
  gc = e.gc
  gc.setBackground red
  gc.fillOval 25, 25, 100, 100
end
cs2.add_paint_listener(blk)

# Button
b = Swt::Widgets::Button.new cs0, Swt::SWT::PUSH
b.setText 'button'
b.pack

# Animation
class Anim
  def initialize &blk
    @blk = blk
  end
  def run
    @blk.call
    $display.timerExec 100, self
  end
end

i = 0
a = Anim.new do
  i+=1
  b.setLocation i, i
  shell.redraw
end
display.timerExec 100, a

shell.pack
shell.open

Swt.event_loop{Swt.display.isDisposed}

Can you make the button go in between the circles?

@ashbb
Copy link
Member

ashbb commented Jun 29, 2012

@wasnotrice Awesome!
We don't have to make the button go in between the circles. Because the Shoes window has just only one layer. It draws/paints all elements on the layer in the order of coding. But Shoes::Native elements, i.e. Button, EditLine, etc., is a different story. Try out the following snippet with Shoes 3. ;-)

Shoes.app do
  nostroke
  oval 0, 0, 100, 100, fill: red
  stack left: 50, top: 50 do
    oval 0, 0, 100, 100, fill: green
  end
  b = button 'button'
  animate do |i|
    b.move i, i
  end
end

Okay, next up is:

I made ShoesComposite not inherit from Swt::Composite because that I was getting errors about not being able to convert a ruby object into an Swt object.

Umm,... I also got an error. But that was "ArgumentError: wrong number of arguments for constructor". So I edited your code like this. Then it worked.

class ShoesComposite < Swt::Widgets::Composite
  def initialize *cs
    @cs = cs[0]
    super cs[0], Swt::SWT::NULL 
  end

@PragTob
Copy link
Member

PragTob commented Jul 1, 2012

Man you guys are working like crazy, thank you so much!

Tried out the last snippet from @wasnotrice with and without the modification by @ashbb - both work fine on my Linux distribution (Linux Mint Debian Edition with Cinnamon as the desktop manager). Great work!

@wasnotrice
Copy link
Member Author

Just to complicate things :P

Here's a version of this snippet written with Qt on OS X and Linux. Qt seems _much_ more complete than Swt. This code is running on MRI 1.9.3, which gives up some JRuby benefits.

require 'Qt'

class Color
  def self.white
    Qt::Color.new(255, 255, 255)
  end

  def self.red
    Qt::Color.new(255, 0, 0)
  end

  def self.green
    Qt::Color.new(0, 255, 0)
  end
end

class Slot < Qt::Widget
  def initialize(parent)
    super
    @paint_blocks = []
  end

  def paint(&block)
    @paint_blocks << block
  end

  def paintEvent(event)
    painter = Qt::Painter.new(self)
    painter.setRenderHint Qt::Painter::Antialiasing
    @paint_blocks.each do |b|
      b.call(painter)
    end
    painter.end
  end
end

class ShoesButton < Qt::PushButton
  slots :move
  def move()
    self.setGeometry Qt::Rect.new(Qt::Point.new(self.x + 1, self.y + 1), self.size)
  end
end

app = Qt::Application.new([])
window = Qt::Widget.new
palette = window.palette
palette.setColor(Qt::Palette::Window, Color.white)
window.palette = palette


# Green circle
slot1 = Slot.new(window)
slot1.setGeometry 0, 0, 200, 200
slot1.paint do |painter|
  painter.setBrush(Qt::Brush.new Color.green)
  painter.setPen(Qt::NoPen)
  painter.setOpacity 0.5
  painter.drawEllipse(25, 25, 100, 100)
end

# Button
button = ShoesButton.new "Button", window

# Red circle
slot2 = Slot.new(window)
slot2.setGeometry 50, 50, 200, 200
slot2.paint do |painter|
  painter.setBrush(Qt::Brush.new Color.red)
  painter.setPen(Qt::NoPen)
  painter.setOpacity 0.5
  painter.drawEllipse(25, 25, 100, 100)
end

# Animation
timer = Qt::Timer.new(window)
timer.connect(timer, SIGNAL('timeout()'), button, SLOT('move()'))
timer.start 100

window.show
window.raise
app.exec

@ashbb
Copy link
Member

ashbb commented Jul 3, 2012

Cool!
I'd like to run the code on my Windows 7. How to install Qt?
The gem install Qt is available?

@PragTob
Copy link
Member

PragTob commented Jul 3, 2012

@ashbb It would surprise me if it were that easy, a short google search turned up this promising stackoverflow discussion

However if you are to install WT beforehand (not only the gem) take caution, the first option is awfully big, the libraries are farther down on the download page

@ashbb
Copy link
Member

ashbb commented Jul 3, 2012

@PragTob Oh, umm,... I can accept some annoying install steps for developing Shoes 4, but may not be able to accept for using Shoes 4...

@wasnotrice
Copy link
Member Author

@ashbb looks like it should be as easy as gem install qtbindings on windows. See the answer from @PragTob's discussion.

I have been recording steps to get setup with OS X and Linux at my shoes-qt repo. Also very simple.

I can accept some annoying install steps for developing Shoes 4, but may not be able to accept for using Shoes 4...

Yes, we should be conscious of this. We would have to bundle Qt with Shoes, like we have been bundling cairo and pango with Shoes3.

There are Java bindings for Qt, at the Qt-Jambi project, so it seems possible to run Qt from JRuby. But I could not get these to work on my machine, and they are running at least a version behind the Qt project, so might not be a reliable resource. Straight Qt was very simple to setup.

@davorb
Copy link
Member

davorb commented Jul 3, 2012

Have you guys considered doing this by drawing a bunch of lines ontop of each other until they form circles?

I realize how assinine that sounds, but you have to do something similar in HTML5 Canvas if you want to draw circles.

<script>
  context.arc(x, y, radius, 0 , 2 * Math.PI, false);
</script>

(Yes, the only way to draw a circle with the canvas api is by drawing an arc... sigh)

@wasnotrice
Copy link
Member Author

Drawing the circle is not the problem. The problem is that swt widgets all have opaque backgrounds on Linux :(

@ashbb just happened to use a circle in his snippet exploring the problem :)

@davorb
Copy link
Member

davorb commented Jul 4, 2012

You mean that the backgrounds aren't transparent?

@PragTob
Copy link
Member

PragTob commented Jul 4, 2012

Yep like that, take a look at the screenshot

@PragTob PragTob mentioned this issue Jul 4, 2012
@PragTob
Copy link
Member

PragTob commented Jul 4, 2012

About the topic of Qt - Qt is indeed pretty complete (I'm currently working with Qt at university, unfortunately with C++ :-/) and the docs are some of the best I've ever seen. The question if we would really want to switch the backend or start the development of a new backend in parallel to SWT.

Giving up JRuby would be hard though... well no matter what, I like Qt as a possible backend for shoes in the future :-)

@ashbb
Copy link
Member

ashbb commented Jul 4, 2012

@wasnotrice @PragTob

Oops, I didn't read the answer. xx-P
I could install Qt by just one step: gem install qtbindings
Super easy!

Now the above @wasnotrice's snippet works fantastic on my Windows 7 with Ruby 1.9.3. Awesome!

@wasnotrice
Copy link
Member Author

@ashbb Great! :)

So...now we have some big decisions to make. On the ML?

@PragTob
Copy link
Member

PragTob commented Jul 4, 2012

Yep I'm all for ML. I guess/hope more people notice it that way... however we should also make an issue here referring to the ML (or the other way around, so both channels get the news)

@davorb
Copy link
Member

davorb commented Jul 4, 2012

@PragTob In that case "draw circles using lines" will solve that problem.

Are there really any other elements where this is an issue?

@PragTob
Copy link
Member

PragTob commented Jul 4, 2012

@davorb The problem is rather that we wanted to use Swt::Composite in order to implement stack and flow, which have to be transparent by definition - so this is a huge drawback.

@davorb
Copy link
Member

davorb commented Jul 4, 2012

@PragTob Ah, I see... Thanks for explaining it to me.

@ozpos
Copy link

ozpos commented Jul 13, 2012

@ashbb
Copy link
Member

ashbb commented Jul 14, 2012

@ozpos Thanks for the information. That's interesting. But we may need another solution, because we are finding how to inprement Set::Composite with transparent background, not Swt::Label.

@davorb
Copy link
Member

davorb commented Jul 17, 2012

You guys think doing something similar to what this guy (Alun) is suggesting might work? http://www.eclipsezone.com/eclipse/forums/t23689.html

@ashbb
Copy link
Member

ashbb commented Jul 17, 2012

@davorb We don't want to see the background of Composite through the controls. We want to make the background of Composite itself transparent. So, I think we need an other solution...

@wasnotrice
Copy link
Member Author

Closing as fixed. See #103.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants