Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Animated cubics at last(cubic_demo05)

Notice: a severe qtruby bug may be present. Patch is inside this commit.
  • Loading branch information...
commit 2889b982d281ed1d3525e83e0cdc46af425fd5f7 1 parent 97b43ef
@EugeneBrazwick authored
View
4 README
@@ -19,6 +19,10 @@ BUILD: just run
rake
in the toplevel dir.
+Hm.. qtruby4.6.3.1 contains a bug. A simple 'Qt::Variant::new(0.0)' immediately fails!
+To fix patch it:
+ sudo patch -p0 -E < qtruby4631.patch
+
INSTALL: cannot be done yet. But isn't really required at the moment.
Current state (oct 6 2010)
View
4 lib/reform/abstractAction.rb
@@ -20,11 +20,11 @@ def initialize parent, qtc
# notice: these can no longer be queried....
def enabler value = nil, &block
- DynamicAttribute.new(self, :enabled, value, &block)
+ DynamicAttribute.new(self, :enabled, TrueClass, value, &block)
end
def disabler value = nil, &block
- DynamicAttribute.new(self, :disabled, value, &block)
+ DynamicAttribute.new(self, :disabled, TrueClass, value, &block)
end
public
View
19 lib/reform/animation.rb
@@ -33,7 +33,14 @@ def postSetup
end
end
- protected
+ define_simple_setter :loopCount
+
+ def looping
+ @qtc.loopCount = -1
+ end
+
+ protected # Animation methods
+
attr_writer :autostart
def autostart value = nil
@@ -44,13 +51,17 @@ def autostart value = nil
def autostart?
@autostart
end
- public
+
+ public # Animation methods
+
def addTo parent, hash, &block
parent.addAnimation self, hash, &block
end
def addAnimation anim, hash, &block
super
+# tag "appending #{anim.qtc} to group #@qtc"
+ @qtc.addAnimation(anim.qtc)
anim.autostart = false
end
@@ -59,5 +70,5 @@ def postSetup
end
- end
-end
+ end #class Animation
+end # module Reform
View
24 lib/reform/animations/attributeanimation.rb
@@ -11,7 +11,8 @@ class AttributeAnimation < Animation
def initialize parent, qtc
super
# tag "#{self}.new(#{parent}, #{qtc}), setting propertyName of anim to 'value'"
- @qtc.setTargetObject(parent)
+ @qtc.setTargetObject(dynamicParent)
+# tag "TargetObject = #{@qtc.targetObject}"
@qtc.propertyName = 'value' # See DynamicAttribute
end
@@ -32,17 +33,32 @@ def states stateid_value_hash
end
end
+ # it seems to me that easing should be set per statevalue. And Qt uses the trajectory
+ # between states...
+ EasingMap = { linear: Qt::EasingCurve::Linear,
+ quad: Qt::EasingCurve::InOutQuad,
+ cubic: Qt::EasingCurve::InOutCubic,
+ sine: Qt::EasingCurve::InOutSine,
+ elastic: Qt::EasingCurve::OutElastic,
+ back: Qt::EasingCurve::OutBack,
+ bounce: Qt::EasingCurve::OutBounce
+ }
+
def easing v
- @qtc.easingCurve = Qt::EasingCurve::OutElastic
+ @qtc.easingCurve = case v
+ when Qt::EasingCurve then v
+ when Symbol then Qt::EasingCurve.new(EasingMap[v] || Qt::EasingCurve::Linear)
+ else Qt::EasingCurve.new(v)
+ end
end
def startValue *value
# tag "start, attrib=#@attrib"
- @qtc.startValue = parent.value2variant(*value)
+ @qtc.startValue = @qtc.targetObject.value2variant(*value)
end
def stopValue *value
- @qtc.endValue = parent.value2variant(*value)
+ @qtc.endValue = @qtc.targetObject.value2variant(*value)
end
# alias :startValue :start
View
48 lib/reform/control.rb
@@ -58,12 +58,12 @@ def initialize attrib
=end
class DynamicAttribute < Control
- private
+ private # DynamicAttribute methods
- def initialize parent, propertyname, quickyhash = nil, &block
+ def initialize parent, propertyname, klass, quickyhash = nil, &block
# tag "DynamicAttribute.new(#{parent}, :#{propertyname})"
super(parent)
- @propertyname = propertyname
+ @propertyname, @klass = propertyname, klass
setup(quickyhash, &block) if quickyhash || block
end
@@ -75,6 +75,12 @@ def through_state states2values
end
end
+ def sequence quickyhash = nil, &block
+ require_relative 'animations/sequentialanimation'
+ setProperty('value', value2variant(:default))
+ SequentialAnimation.new(self, Qt::SequentialAnimationGroup.new(self)).setup(quickyhash, &block)
+ end
+
def animation quickyhash = nil, &block
require_relative 'animations/attributeanimation'
# tag "Creating Qt::Variant of value"
@@ -102,25 +108,31 @@ def event e
end
def value2variant *value
- case @propertyname
- when :brush
+ case
+ when @klass == Qt::Brush
color = Graphical.color(*value)
# tag "Qt::Variant.new(#{color})"
Qt::Variant::fromValue(color)
- when :geometry
+ when @klass == Qt::Rect
Qt::Variant::fromValue(case value[0]
when :default then Qt::Rect.new
when Qt::Rect then value[0]
else Qt::Rect.new(*value) #.tap{|r| tag "creating value(#{r.inspect})"}
end)
- when :geometryF
+ when @klass == Qt::RectF
Qt::Variant::fromValue(case value[0]
when :default then Qt::RectF.new
when Qt::RectF then value[0]
else Qt::RectF.new(*value) #.tap{|r| tag "creating value(#{r.inspect})"}
end)
+ when @klass == Float
+# debug Qt::DebugLevel::High do
+ f = value[0] == :default ? 0.0 : value[0]
+# tag "value2variant, value = #{value.inspect}, f = #{f.inspect}, #{f.class}"
+ Qt::Variant::new(f)
+# end
else
- raise Error, tr("Not implemented: animation for property '#@propertyname'")
+ raise Error, tr("Not implemented: animation for property '#@propertyname', klass=#@klass")
end
end
@@ -139,6 +151,10 @@ def applyModel data, model = nil
# properties 'value'
+ def dynamicParent
+ self
+ end
+
end # class DynamicAttribute
DynamicProperty = DynamicAttribute
@@ -194,8 +210,7 @@ def geometry x = nil, y = nil, w = nil, h = nil, &block
q = effective_qwidget
return q.geometry unless x || w || block
case x
- when nil then DynamicAttribute.new(self, :geometry).setup(nil, &block)
- when Hash, Proc then DynamicAttribute.new(self, :geometry).setup(x, &block)
+ when nil, Hash, Proc then DynamicAttribute.new(self, :geometry, Qt::Rect).setup(x, &block)
else
# @requested_size = w, h
if x or y
@@ -254,6 +269,14 @@ def no_signals
end
end
+ def debug level
+ old = Qt::debug_level
+ Qt::debug_level = level
+ yield
+ ensure
+ Qt::debug_level = old
+ end
+
# shortcut. executes the block every ms milliseconds
def timer_interval timeout_in_ms, &block
start_timer(timeout_in_ms)
@@ -772,6 +795,11 @@ def geometry=(*value)
alias :containingForm :containing_form
+ # same as containingForm
+ def dynamicParent
+ parent.dynamicParent
+ end
+
# Qt control that is wrapped
attr :qtc
View
61 lib/reform/examples/animation/demo06.rb
@@ -6,6 +6,8 @@
Same as demo05, but with two animations
+ BROKEN!!! FIXME
+
=end
require 'reform/app'
@@ -13,38 +15,41 @@
statemachine {
states :s1, :s2, :s3
}
- parallelanimation name :anim
- scene {
- name :myScene
- rect {
- brush {
-# tag "Creating animation :anim"
- animation {
- appendto :anim
- states s1: :blue, s2: :red, s3: :yellow
- duration 1000.ms
+ mainwindow {
+
+ parallelanimation name: :anim
+ scene {
+ name :myScene
+ rect {
+ brush {
+ # tag "Creating animation :anim"
+ animation {
+ appendto :anim
+ states s1: :blue, s2: :red, s3: :yellow
+ duration 1000.ms
+ }
}
}
- }
- rect {
- brush {
-# tag "Creating animation :anim"
- animation {
- appendto :anim
- states s1: :blue, s2: :yellow, s3: :red
- duration 1000.ms
+ rect {
+ brush {
+ # tag "Creating animation :anim"
+ animation {
+ appendto :anim
+ states s1: :blue, s2: :yellow, s3: :red
+ duration 1000.ms
+ }
}
}
}
- }
- button {
- text tr('Click Me')
- whenClicked transitions [{from: :s1, to: :s2, animation: :anim},
- {from: :s2, to: :s3, animation: :anim},
- {from: :s3, to: :s1, animation: :anim}]
- }
- canvas {
- sizeHint 200
- scene :myScene
+ button {
+ text tr('Click Me')
+ whenClicked transitions [{from: :s1, to: :s2, animation: :anim},
+ {from: :s2, to: :s3, animation: :anim},
+ {from: :s3, to: :s1, animation: :anim}]
+ }
+ canvas {
+ sizeHint 200
+ scene :myScene
+ }
}
}
View
40 lib/reform/examples/painting/cubics_demo01.rb
@@ -0,0 +1,40 @@
+
+# Copyright (c) 2010 Eugene Brazwick
+
+=begin
+ An important part of midibox will be the connection drawing using cubics.
+
+ This should be as automatic as possible
+
+ Let's develop some API.
+
+ Painterpath is a good starting point and a way to
+ display these is by constructing a Qt::GraphicsPathItem.
+ It would be nice if you could build the path.
+=end
+
+require 'reform/app'
+
+# introducing graphicspath
+
+Reform::app {
+ mainwindow {
+ sizeHint 400, 320
+ canvas {
+ # graphicspath is calculated once and it will be possible to duplicate them
+ # with a different matrix.
+ # However, you cannot change a part of the path. Only set a completely new path.
+# tag "calling #{self}::pen"
+ pen {
+# tag "self=#{self}"
+ join :round
+ size 7
+ }
+ graphicspath {
+# ellipse position: [100, 100], size: [200, 100]
+ line 0,0, 100,0, 100,100, 0,100, :close
+ # graphicspath ..... recursion should be possible
+ }
+ }
+ }
+}
View
38 lib/reform/examples/painting/cubics_demo02.rb
@@ -0,0 +1,38 @@
+
+# Copyright (c) 2010 Eugene Brazwick
+
+=begin
+ An important part of midibox will be the connection drawing using cubics.
+
+ This should be as automatic as possible
+
+ Let's develop some API.
+
+ Painterpath is a good starting point and a way to
+ display these is by constructing a Qt::GraphicsPathItem.
+ It would be nice if you could build the path.
+
+ -------------------------------
+ Activepath.
+ The fun starts now!
+
+=end
+
+
+require 'reform/app'
+
+Reform::app {
+ mainwindow {
+ sizeHint 400, 320
+ canvas {
+ pen {
+ join :round
+ size 7
+ }
+ # the vertices on an activepath can be moved freely
+ activepath {
+ line 0,0, 100,0, 100,100, 0,100, :close
+ }
+ }
+ }
+}
View
25 lib/reform/examples/painting/cubics_demo03.rb
@@ -0,0 +1,25 @@
+
+# Copyright (c) 2010 Eugene Brazwick
+
+# Extending cubics_demo02
+# This demonstrates an autosmooth curve
+
+#Autosmoothing: code inspired from inkscape source: void Node::_updateAutoHandles()
+# GPL-ed.
+
+require 'reform/app'
+
+Reform::app {
+ mainwindow {
+ sizeHint 400, 320
+ canvas {
+ pen {
+ join :round
+ size 3
+ }
+ graphicspath {
+ smooth 0,0, 100,0, 100,100, 0,100, :close
+ }
+ }
+ }
+}
View
21 lib/reform/examples/painting/cubics_demo04.rb
@@ -0,0 +1,21 @@
+
+# Copyright (c) 2010 Eugene Brazwick
+
+# Extending cubics_demo03 as demo2 related to demo1
+
+require 'reform/app'
+
+Reform::app {
+ mainwindow {
+ sizeHint 400, 320
+ canvas {
+ pen {
+ color :blue
+ cosmetic
+ }
+ activepath {
+ smooth 0,0, 100,0, 100,100, 0,100, :close
+ }
+ }
+ }
+}
View
63 lib/reform/examples/painting/cubics_demo05.rb
@@ -0,0 +1,63 @@
+
+# This demonstrates how to use animate single vertices.
+# How to autocurve? Since controllpoints are a bit hard to animate
+# an autosmoothing curve, together with animation, should make it possible
+# to easily morph between two shapes.
+# We need a :shapeRecipy. Define a shape but do not display it yet.
+# Define two of them, assign 1 to the scene, and then add an animation
+# from the first to the second and back again.
+# This would make a pseudo 'circle' from the cube.
+
+# First I need 'tension' or 'cuspness' that tweaks the autosmoothing.require 'reform/app'
+
+require 'reform/app'
+
+# this demo needs qtruby4631.patch to be applied!!!
+# Use sudo patch -p0 -E < qtruby4631.patch
+# Diagnostics:
+# NoMethodError: undefined method `smoke' for nil:NilClass
+
+Reform::app {
+ mainwindow {
+ sizeHint 640, 480
+ canvas {
+ area -50, -50, 200, 200
+ scale 1.5
+ pen {
+ color :blue
+ size 8
+ }
+ graphicspath {
+ tension {
+ sequence { # naming is inconsistent. That's because we are in DynamicAttribute now...
+ # Only attributeanimation constructions are possible this way.
+ # However 'sequence' DOES call sequentialanimation anyway!
+ looping # same as loopCount -1
+ attributeanimation {
+ from 0.0
+ to 4.0
+ duration 2.seconds
+ easing :bounce
+ }
+ attributeanimation {
+ from 4.0
+ to 0.0
+ easing :elastic
+ duration 2.seconds
+ }
+ # AttributeAnimation in itself has code to store an array of values with timing points.
+ # 'from' is used 0.0=>value
+ # and 'to is 1.0=>value
+ # Or an array of points with equal times in between:
+ # values 0.0, 2.0, 0.0
+ # It should be possible to supply a hash in one go.
+ # And we can drop the 'sequence' around it.
+ # demo06 can show this + rotations + colorhue rotation...
+ # Also in demo06: we should allow AnimationContext in DynamicAttribute
+ }
+ }
+ smooth 0,0, 100,0, 100,100, 0,100, :close
+ }
+ }
+ }
+}
View
170 lib/reform/graphical.rb
@@ -9,7 +9,95 @@ module Reform
# these will be used when rendering shapes on a graphicsitem, canvas or scene.
module Graphical
extend Graphical
- private
+
+ class Brush < Control
+ include Graphical
+ private
+ def initialize
+ super(nil, Qt::Brush.new)
+ end
+
+ # makes it a solid brush
+ def color *args
+ @qtc = make_brush(make_color(*args))
+ end
+
+ public
+ def name aName = nil # not supported since Qt::Brush is not a Qt::Object at all. And we already index them too
+ end
+
+ end # class Brush
+
+ class Pen < Control
+ include Graphical
+ def initialize
+ super(nil, Qt::Pen.new)
+ end
+
+ def color *args
+ @qtc = make_pen(make_color(*args))
+ end
+
+ define_simple_setter :widthF
+
+# # width can be used with a float. If 0.0 the pen is cosmetic
+ def width value
+ case value
+ when Fixnum then @qtc.width = value
+ else @qtc.widthF = value
+ end
+ end
+
+ alias :size :width
+
+ # make the size 1 pixel, independent of scale
+ def cosmetic v_true = true
+ @qtc.widthF = 0.0
+ end
+
+ JoinMap = { :miter => Qt::MiterJoin, :bevel => Qt::BevelJoin, :roundjoin => Qt::RoundJoin,
+ :round => Qt::RoundJoin }
+
+ def joinStyle value
+ value = JoinMap[value] || Qt::MiterJoin if Symbol === value
+ @qtc.joinStyle = value
+ end
+
+ alias :join :joinStyle
+
+ CapMap = { :square => Qt::SquareCap, :flat => Qt::FlatCap, :round => Qt::RoundCap,
+ :roundcap => Qt::RoundCap }
+
+ def capStyle value
+ @qtc.capStyle = Symbol === value ? CapMap[value] || Qt::FlatCap : value
+ end
+
+ StyleMap = { :solid => Qt::SolidLine, :dash => Qt::DashLine, :dot => Qt::DotLine,
+ :dashdot => Qt::DashDotLine, :dashdotdot => Qt::DashDotDotLine,
+ :custemdash => Qt::CustomDashLine }
+
+ # example: style :roundjoin, :roundcap, :solid
+ # which is the same as style :round, :solid
+ def style *values
+ values.each do |value|
+ case value
+ when Symbol
+ v = JoinMap[value] and @qtc.joinStyle = v
+ v = CapsMap[value] and @qtc.capStyle = v
+ v = StyleMap[value] and @qtc.style = v
+ else
+ @qtc.style = value
+ end
+ end
+ end
+
+ public
+ def name aName = nil
+ end
+ end # class Pen
+
+
+ private # Graphical methods
Qt::Color::allowX11ColorNames = true #rescue nil
@@ -22,7 +110,7 @@ def self.generateColorConverter name, klass, cache
when Symbol
cache[colorsym] ||= klass.new(@@color[colorsym])
else
- klass.new(color(colorsym, *more))
+ klass.new(make_color(colorsym, *more))
end
end
end
@@ -64,7 +152,7 @@ def self.colorkeys
end
# returns a Qt::Color. The heart of colorknowledge on earth
- def color colorsym, g = nil, b = nil, a = nil
+ def make_color colorsym, g = nil, b = nil, a = nil
case colorsym
# when Qt::Color, Qt::ConicalGradient, Qt::LinearGradient, Qt::RadialGradient then colorsym
when Qt::Color then colorsym
@@ -108,7 +196,7 @@ def color colorsym, g = nil, b = nil, a = nil
end
end
- alias :make_color :color
+ alias :color :make_color
generateColorConverter :color2pen, Qt::Pen, @@pen # DEPRECATED
generateColorConverter :color2brush, Qt::Brush, @@solidbrush # DEPRECATED
@@ -118,36 +206,58 @@ def color colorsym, g = nil, b = nil, a = nil
# make_pen :blue
# make_pen blue
# The symbol param is the only one that caches the result.
- def make_pen colorsym, *more
- colorsym = colorsym.qtc if colorsym.respond_to?(:qtc)
- case colorsym
- when Qt::Pen then colorsym
- when nil, false then @@pen[:none] ||= Qt::Pen.new(Qt::NoPen)
- when Symbol then @@pen[colorsym] ||= Qt::Pen.new(@@color[colorsym])
- else Qt::Pen.new(color(colorsym, *more))
+ def make_pen *args, &block
+ args = args[0] if args.length <= 1
+ args = args.qtc if args.respond_to?(:qtc)
+ case args
+ when Qt::Pen then args
+ when false, :none, :no_pen then @@pen[:none] ||= Qt::Pen.new(Qt::NoPen)
+ when nil
+ if block
+ Pen.new.setup(nil, &block).qtc
+ else
+ @@pen[:none] ||= Qt::Pen.new(Qt::NoPen)
+ end
+ when Symbol
+ col = @@color[args] or raise Error, ":#{args} is not a valid colorsymbol, use #{@@color.keys.inspect}"
+ @@pen[args] ||= Qt::Pen.new(col)
+ when Hash then Pen.new.setup(args, &block).qtc
+ when Array then Qt::Pen.new(make_color(*args))
+ when Qt::Color then Qt::Pen.new(args)
+ else Qt::Pen.new(make_color(args))
end
end
# convert anything into a Qt::Brush
- def make_brush colorsym, *more
+ def make_brush *args, &block
+ args = args[0] if args.length <= 1
# tag "make_brush #{colorsym}, #{more.inspect}"
- colorsym = colorsym.qtc if colorsym.respond_to?(:qtc)
+ args = args.qtc if args.respond_to?(:qtc)
# tag "colorsym = #{colorsym.inspect}"
- case colorsym
- when Qt::Brush then colorsym
- when nil, false then @@solidbrush[:none] ||= Qt::Brush.new(Qt::NoBrush)
+ case args
+ when Qt::Brush then args
+ when false, :none, :no_brush then @@solidbrush[:none] ||= Qt::Brush.new(Qt::NoBrush)
+ when nil
+ if block
+ Brush.new.setup(nil, &block).qtc
+ else
+ @@solidbrush[:none] ||= Qt::Brush.new(Qt::NoBrush)
+ end
when Symbol
# tag "locating :#{colorsym}, working through @@color #{@@color.inspect}"
- col = @@color[colorsym] or raise Error, ":#{colorsym} is not a valid colorsymbol, use #{@@color.keys.inspect}"
- @@solidbrush[colorsym] ||= Qt::Brush.new(col) #) .tap{|b| tag "returning #{b}"}
- when Qt::RadialGradient, Qt::LinearGradient, Qt::ConicalGradient then Qt::Brush.new(colorsym)
+ col = @@color[args] or raise Error, ":#{args} is not a valid colorsymbol, use #{@@color.keys.inspect}"
+ @@solidbrush[args] ||= Qt::Brush.new(col) #) .tap{|b| tag "returning #{b}"}
+ when Qt::RadialGradient, Qt::LinearGradient, Qt::ConicalGradient then Qt::Brush.new(args)
+ when Hash then Brush.new.setup(args, &block).qtc
when String
- if colorsym[0, 7] == 'file://'
- Qt::Brush.new(Qt::Pixmap.new(colorsym[7..-1]))
+ if args[0, 7] == 'file://'
+ Qt::Brush.new(Qt::Pixmap.new(args[7..-1]))
else
- Qt::Brush.new(color(colorsym))
+ Qt::Brush.new(make_color(args))
end
- else Qt::Brush.new(color(colorsym, *more))
+ when Array then Qt::Brush.new(make_color(*args))
+ when Qt::Color then Qt::Brush.new(args)
+ else Qt::Brush.new(make_color(args))
end
end
@@ -157,12 +267,12 @@ def make_brush colorsym, *more
# with a single color it is that color to black (left to right)
def gradient w, *brushes
g = Qt::LinearGradient.new(0.0, 0.0, w, 0.0)
- brushes[0] = color(:white) if brushes.empty?
- brushes[1] = color(:black) if brushes.length < 2
+ brushes[0] = make_color(:white) if brushes.empty?
+ brushes[1] = make_color(:black) if brushes.length < 2
d = 1.0 / brushes.length
j = 0.0
brushes.each do |b|
- g.setColorAt j, color(b)
+ g.setColorAt j, make_color(b)
j += d
end
Qt::Brush.new g
@@ -178,7 +288,7 @@ def initialize klass
def stop hash # FIXME: 1-liner
offset = [[hash[:offset] || 0.0, 0.0].max, 1.0].min
- col = color(hash[:color] || :white)
+ col = make_color(hash[:color] || :white)
@qtc.setColorAt(offset, col)
end
@@ -189,12 +299,12 @@ def stops hash
# using setStops is very problematic
hash.each do |pt, col|
# tag "#@qtc.setColorAt(#{pt}, #{col})"
- @qtc.setColorAt(pt, color(col))
+ @qtc.setColorAt(pt, make_color(col))
end
else
# tag "setColor 0000000 to fffffff"
- @qtc.setColorAt(0.0, color(:white)) # Qt::Color::fromRgba(0x00000000))
- @qtc.setColorAt(1.0, color(:black)) # Qt::Color::fromRgba(0x00ffffff))
+ @qtc.setColorAt(0.0, make_color(:white)) # Qt::Color::fromRgba(0x00000000))
+ @qtc.setColorAt(1.0, make_color(:black)) # Qt::Color::fromRgba(0x00ffffff))
# AARGHH ArgumentError: Cannot handle 'const QVector<QPair<double,QColor> >&' as argument of QGradient::setStops
#@qtc.stops = [0.0, Qt::Color::fromRgba(0x00000000)], [1.0, Qt::Color::fromRgba(0xffffffff)]]
View
102 lib/reform/graphics/activepath.rb
@@ -0,0 +1,102 @@
+
+# Copyright (c) 2010 Eugene Brazwick
+
+module Reform
+
+ require_relative 'graphicspath'
+
+# extends PathItem with the ability to move the vertices around, keeping the path constraints
+# active.
+# It would be easy now, to extend it with functionality of moving controlpoints as well,
+# but I don't see the use of this, as I'm going to use 'autosmoothing' anyway.
+ class ActivePathItem < PathItem
+
+ # Note that Ellipse uses an offset to paint itself, and these controllers
+ # normally all have position 0,0 (!)
+ # This is inconvenient, so we make the topleft always 0.0
+ class QController < Qt::GraphicsEllipseItem
+ private
+ # note the 'vertex' is in fact a Vertex as supplied by PathBuilder
+ def initialize(parent, vertex, vertex_index, radius)
+ super(-radius, -radius, radius * 2, radius * 2, parent.qtc)
+ self.setPos(vertex.x, vertex.y)
+ @vertex_index, @item = vertex_index, parent # the ActivePathItem itself
+ setFlag(Qt::GraphicsItem::ItemSendsGeometryChanges, true)
+ end
+ public
+ # override.
+ # As a result of cleverly setting the pos, the resulting pos here is
+ # the new value of the vertex we are controlling!
+ def itemChange(itemchange, qvariant)
+ if itemchange == Qt::GraphicsItem::ItemPositionHasChanged
+ pos = qvariant.value
+# tag "notification PosChanged to #{pos.inspect}, self.pos=#{self.pos.inspect}"
+ @item.setVertexPos(@vertex_index, pos.x, pos.y)
+ end
+ qvariant
+ end
+ end # class QController
+
+ private # ActivePathItem methods
+ # we inherit: @path (being build), @first_subpathnode
+ # the trick is that we use a 'Builder' pattern.
+ def initialize parent, qtc
+ super
+ @path = PathBuilder.new(self)
+ @ctrlr_radius = 5.0
+ @ctrlr_pen = Graphical::make_pen(:red)
+ @ctrlr_brush = Graphical::make_brush(:no_brush)
+ @ctrlr_cursor = Qt::PointingHandCursor
+ end
+
+ def controller_radius x
+ @ctrlr_radius = x
+ end
+
+ CursorMap = { arrow: Qt::ArrowCursor, up_arrow: Qt::UpArrowCursor, cross: Qt::CrossCursor,
+ wait: Qt::WaitCursor, ibeam: Qt::IBeamCursor, size_ver: Qt::SizeVerCursor,
+ size_hor: Qt::SizeHorCursor, size_bdiag: Qt::SizeBDiagCursor,
+ size_fdiag: Qt::SizeFDiagCursor, blank: Qt::BlankCursor, splitv: Qt::SplitVCursor,
+ splith: Qt::SplitHCursor,
+ pointing_hand: Qt::PointingHandCursor, forbidden: Qt::ForbiddenCursor,
+ open_hand: Qt::OpenHandCursor, closed_hand: Qt::ClosedHandCursor,
+ whats_this: Qt::WhatsThisCursor, busy: Qt::BusyCursor }
+
+ def controller_cursor x
+ @ctrlr_cursor = Symbol === x ? CursorMap[x] || Qt::ArrowCursor : x
+ end
+
+ def controller_pen(*args, &block)
+ @ctrlr_pen = make_pen(*args, &block)
+ end
+
+ def controller_brush(*args, &block)
+ @ctrlr_brush = make_brush(*args, &block)
+ end
+
+ def assignQPath
+ @qtc.path = @path.build
+ for v, i in @path.each_vertex.each_with_index
+# tag "? v = #{v.class}-(#{v.x}, #{v.y})"
+ el = QController.new(self, v, i, @ctrlr_radius)
+ el.pen, el.brush = @ctrlr_pen, @ctrlr_brush
+ el.cursor = Qt::Cursor.new(@ctrlr_cursor)
+ el.setFlag Qt::GraphicsItem::ItemIsMovable, true
+ end
+ end
+
+ public
+
+ # callback, request to alter index.
+ def setVertexPos vertex_index, x, y
+ @path[vertex_index].pos = x, y
+ @qtc.path = @path.path
+ end
+
+ end # class ActivePathItem
+
+ createInstantiator File.basename(__FILE__, '.rb'), Qt::GraphicsPathItem, ActivePathItem
+# tag "test for Scene#circle"
+# raise ReformError, 'oh no' unless Scene.private_method_defined?(:circle)
+
+end # Reform
View
525 lib/reform/graphics/graphicspath.rb
@@ -0,0 +1,525 @@
+
+=begin
+The node-smoothing algorhytm stems from src/ui/tool/node.cpp from the inkscape source,
+with the folling copyright notice:
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+=end
+
+module Reform
+
+ require 'reform/graphicsitem'
+
+=begin
+
+Now, Eugene has a phylosofical argument with methods like 'lineTo'.
+As these implement a change, and not a declaration.
+
+Especially 'moveTo'.
+
+A path can be seen as a series of segments where the pen stays on the canvas
+while adding stuff.
+Let's represent that with the [] array notation.
+
+This implies that adding two arrays like [...], [...] will cause a moveTo to be
+implied.
+In that terminology a line is just a vertex. And a vertex can be represented as a tuple
+[x, y].
+
+[[0,0],[1,0],[1,1]],[0,0],[-1,0],[0,-1]]
+
+I we know we are dealing with a path and not a vertex then we can also write
+[0,0, 1,0, 1,1],[0,0, -1,0, 0,-1]
+
+Then we must decide how a path is 'closed'.
+I would say that 'closing' is the default case, and leaving it open is special.
+To do this the single element :open is to be used.
+
+In the end only 3 elements exist in a path; move, line and curve.
+
+So [[0,0],[1,1]] is moveTo(0,0), lineTo(1,1)
+Next we can say line(0,0, 1,1) or line([0,0],[1,1]). And to add segments just do:
+ line(0,0, 1,1, 2,0, 1,0)
+However, it should not close automatically.
+ curve
+should work the same, And it is possible to use both as components of paths:
+
+[ line(0,0, 1,1), line(2,0, 1,0)]
+Since an array represents a 'pen down' part, the [2,0] starting the second line is lineTo
+and not a moveTo. In this sense [line(0,0, 1,1), line(2,0)] is identical to
+[0,0, 1,1, 2,0].
+
+To close a line or curve anyway you can use :close as last element. So
+line(0,0, 1,1, 1,0, :close) is equal to [0,0, 1,1, 1,0] or [0,0, 1,1, 1,0, 0,0, :open]
+The last one is :open logically, but closed if we look to the coordinates. I say it should
+be avoided, for this ambiguity. But to create a closed curve we must use it.
+
+So we compose the path of components:
+
+ - vertex, cannot occur on its own. Always part of a polyline
+ - line. If in front then the first one is a move, all others are lineTo, including :close.
+ - curve. Same as line but now curves. However :close is a lineTo.
+ - :close. lineTo first point of path, plus a moveTo(0,0) Any trailing components are ignored
+ - ellipse. all basic shapes add closed subpaths and implicitely close the previous path.
+ They cannot be put inside paths. [0,0, 1,1], ellipse(....), [....]
+ Also repetition of arguments is not handled.
+ - rect
+ - roundedrect
+ - path
+ - region. A region is not unlike a path, except that only the fill is taken into account.
+ Normally used to define a clippingzone. Instead of adding polygons, you
+ add basic rectangles, ellipses or bitmaps using '|', '&', '-' and '^'
+ - text. Text is put on the coord where x = left (or right), and y the baseline.
+ - polygon. left open
+ - arc. The first coordinate here becomes a moveTo or lineTo, which is the center of
+ the passed rectangle. Followed by startangle (0 == +x) and sweep-angle ccw.
+ at that point on the arc it is left open. So arc can be embedded in [].
+ arc(rect, ang1, angd). Angles are in degrees.
+ - cubic. Similar to arc. cubic(p1, c1, c2, p2, c3, c4, p3, ....)
+ [line(p1, p2), line(p3, p4)] the p3 is a lineTo(p3)
+ [cubic(p1, c1,c2, p2), cubic(p3, c1, c2, p4)]. p3 is also a lineTo(p3)
+ cubic(p1, c1,c2, p2), cubic(p3, c1, c2, p4). p3 is a moveTo(p3)
+ line(p1, p2), cubic(p2, c1, c2, p3). We must duplicate the vertex. But:
+ [p1, cubic(p2, c1,c2, p3)] is the same.
+ - quad. Second degree version of cubic (faster but less fluid).
+ quad(p1, c2, p2, c2, p3, ...)
+ - subpath. Same as []
+
+Lacking: arcMoveTo (moveTo point on arc, based on the angle and boundingrect)
+
+Paths can be outlined, creating another path.
+Also: intersection (&), union (|) or substraction.
+In the end a path can only use 1 pen and 1 brush.
+Translation and matrix mapping can also be done.
+
+=end
+ class PathItem < GraphicsItem
+
+ # API compatible with Qt::PainterPath.
+ # Actually we duplicate code since Qt::PainterPath already has internal elements.
+ # However, there is no API to make changes to it, nor to filter elements.
+ # PathBuilder makes it possible to create active vertices on the path, that
+ # can be moved by the user, or animated.
+ class PathBuilder
+
+ # A single vertex can be usefull to designate a nonsmooth part.
+ class Vertex
+ private
+ def initialize x = 0, y = 0, kind = :sharp, c_prev = nil, c_next = nil
+ @x, @y, @kind = x, y, kind
+ # if smooth it has two additional controlpoints
+ @c_prev, @c_next = c_prev, c_next
+ end
+
+ def self.normalize(x, y)
+ len = Math::hypot(x, y)
+ return x, y if len <= 0.000_000_1 || len.nan?
+ [x / len, y / len] #.tap{|p|tag "normalize(#{x}, #{y}) -> (#{p.inspect})"}
+ end
+
+ public
+
+ def smooth?
+# @c_prev || @c_next || @kind == :smooth
+ @kind == :smooth
+ end
+
+ def endpoint?
+ @kind == :end
+ end
+
+ def startpoint?
+ @kind == :start
+ end
+
+ attr :x, :y
+
+ def v
+ return x, y
+ end
+
+ def pos= x, y = nil
+ if y
+ @x, @y = x, y
+ else
+ @x, @y = x
+ end
+ end
+
+ def setPosAndKind x, y, kind
+ @x, @y, @kind = x, y, kind
+ end
+
+ def auto_smooth prev_v, next_v, tension
+ nx, ny = next_v.x - x, next_v.y - y
+ px, py = prev_v.x - x, prev_v.y - y
+ len_next = Math::hypot(nx, ny)
+ len_prev = Math::hypot(px, py)
+# tag "v=(#@x,#@y), p=(#{px},#{py})(len:#{len_prev}), n=(#{nx},#{ny})(len:#{len_next})"
+ if len_next > 0.000_000_1 && len_prev > 0.000_000_1
+ d = len_prev / len_next
+ #// "dir" is an unit vector perpendicular to the bisector of the angle created
+ #// by the previous node, this auto node and the next node.
+ dx, dy = Vertex::normalize(d * nx - px, d * ny - py)
+ # // Handle lengths are equal to 1/3 of the distance from the adjacent node.
+ len_prev *= 0.333_333_333_333 * tension
+ len_next *= 0.333_333_333_333 * tension
+ @c_prev = @x - dx * len_prev, @y - dy * len_prev
+ @c_next = @x + dx * len_next, @y + dy * len_next
+ else
+ @c_prev = @c_next = @x, @y
+ end
+ end # auto_smooth
+
+ attr :kind, :c_prev, :c_next
+
+ def createDebugController qparent
+ Qt::GraphicsEllipseItem.new(@x - 4, @y - 4, 8, 8, qparent)
+ Qt::GraphicsLineItem.new(@x, @y, *@c_prev, qparent) if @c_prev
+ Qt::GraphicsLineItem.new(@x, @y, *@c_next, qparent) if @c_next
+ end
+ end # class Vertex
+
+ # these are pseudo closed paths. They have no influence on vertexlists
+ # and leave the endpoint at the last point drawn. But it would be wise to
+ # start the next segment with a moveTo.
+ class Rectangle
+ end
+
+ class Ellipse
+ end
+
+ class RoundedRectangle
+ end
+
+ class Region
+ end
+
+ class Text
+ end
+
+ private # PathBuilder methods
+
+=begin
+ subpaths are handled ambiguously by Qt. Since closeSubpath always adds a straight line
+ it is pretty useless for curves.
+ So curves need one vertex extra, the endvertex is the same as the first one.
+ See example:
+
+ QPainterPath path;
+ path.addRect(20, 20, 60, 60);
+
+ path.moveTo(0, 0);
+ path.cubicTo(99, 0, 50, 50, 99, 99);
+ path.cubicTo(0, 99, 50, 50, 0, 0);
+
+ The 0,0 vertex is twice in the path.
+ If we make those vertices 'active' it would immediately show up.
+
+ smooth p1, p2, p3
+
+ is a moveTo p1, then cubicTo p2 and cubicTo p3. Since p1 and p3 are endpoints the controlpoints
+ are p1 and p3 themselves.
+
+ smooth p1, p2, p3, :close
+
+ :close is a lineTo p1, and p1 will not be smooth, and neither will p3
+ moveTo p1, cubicTo p2, cubicTo p3, lineTo p1
+
+ smooth p1, p2, p3, :smoothclose
+ moveTo p1, cubicTo p2, cubicTo p3, cubicTo p1
+ That last vertex (p1) does not count as being on the path. So the vertices are p1, p2 and p3.
+ But both p1 and p3 are now smooth.
+
+ line p1, p2, smooth p3, :close
+ When a line connects to a smooth curve the curve bit is semismooth near the endpoints.
+ That is we smooth the node, but there is only one handle to set.
+
+ smooth p1, p2, p3, p1
+ To get a 2 points smooth curve. At p1 the curve is not smooth, since it is twice an
+ endpoint. But this is ugly. I must be able to say that a path is truly closed.
+
+ vertex p1, smooth p2, p3, :close
+
+ I think the rules must bend a little to make it more consistent.
+ The vertices mentioned in a line expression are never smooth.
+ Those mentioned in a smooth are ALL smooth.
+ This implies :smoothclose becomes DEPRECATED. It is meaningless
+
+ vertex p1, smooth p2, p3, :close
+
+ p1 will not be smooth but p2 and p3 are. Nevertheless there are 3 cubics.
+
+ line p1, p2, smooth p3, :close
+
+ the segment p2-p3 must be a cubicTo(p2,p2, c3,p3)
+
+ This also means that adding nodes may change the last one.
+
+ Basicly we add vertices and these can be marked:
+ - start(point). Counts as sharp,
+ - end(point). May be absent if subpath closes. Endpoints do not appear in the 'each_vertex' list
+ Counts as sharp.
+ - sharp. as opposed to smooth
+ - smooth.
+
+ Using this scheme it is possible to add lines, moves etc...
+ To avoid changing classes this just becomes a field of Vertex itself.
+ The path can then be easily generated from this.
+
+ Starting and ending is something else. It must be since line p1, p2
+ is supposed to be different then smooth p1, p2.
+ No, not really. endpoints cannot be smooth. And endpoints and starting points differ too.
+
+ But the status may change.
+ If I start line(p1,p2) and if I add p1 as :start, then how can I see the difference
+ with smooth(p1,p2)? It seems we must delay setting :start and :end until we
+ get a moveTo! If we get a :close we need not do anything after all!
+
+
+ smooth p1, p2 is meaningless. However:
+
+ line p1, p2, smooth p3, line p4
+
+ is possible. Although a better syntax would be
+
+ line p1, p2, smoothTo p3, lineTo p4
+
+ What's the use of 'line' anyway.
+ I would like to say that 'smooth' is a property of a vertex, and that paths are lists
+ of vertices. line v1, v2... just expresses the non-smoothness.
+ But then [v1, v2] would be a better notation.
+ Get into trouble with
+
+ invalid syntax
+ [v1, v2 smooth s3, s4 v5, v6 ]
+
+ Then this:
+ [v1, v2]
+ smooth s3, s4
+ [v5, v6]
+
+ [] will not work properly, obviously. Stupid idea.
+
+ Let's stick to line and smooth for the time being
+=end
+ def initialize item
+ super()
+ @paths = [] # of subpaths
+ @paths << (@currentpath = [Vertex.new])
+ # lower tension (up to 0.0) cause shorter controlpoint-lines
+ # 1.0 fits a length of 1/3 of the in- and outgoing segments.
+ # With 0.0 we get spiked points and basicly no roundness
+ # The higher the tension the harder the edges are pulled outwards.
+ @tension = 1.0
+ end
+
+ def connectToPrev qpath, vertex, prev
+ if vertex.smooth?
+ if prev.smooth?
+# tag "GEN: cubicTo"
+ qpath.cubicTo(*prev.c_next, *vertex.c_prev, vertex.x, vertex.y)
+ else
+# tag "GEN: cubicTo"
+ qpath.cubicTo(prev.x, prev.y, *vertex.c_prev, vertex.x, vertex.y)
+ end
+ elsif prev.smooth?
+# tag "GEN: cubicTo"
+ qpath.cubicTo(*prev.c_next, vertex.x, vertex.y, vertex.x, vertex.y)
+ else
+# tag "GEN: lineTo"
+ qpath.lineTo(vertex.x, vertex.y)
+ end
+ end
+
+ public # PathBuilder methods
+
+ def build
+# tag "path, paths = #{@paths.inspect}"
+ # calculate smooths now.
+ @paths.each do |path|
+ break if (n = path.length) == 1 # which can only be the last one
+ path.each_with_index do |vertex, i|
+ case vertex.kind
+ when :end then vertex.c_prev = vertex.v if path[(i - 1) % n].smooth?
+ when :start then vertex.c_next = vertex.v if path[(i + 1) % n].smooth?
+ when :smooth
+ vertex.auto_smooth(path[(i - 1) % n], path[(i + 1) % n], @tension)
+ end
+ end
+ end
+ qpath = Qt::PainterPath.new
+ @paths.each do |path|
+ break if path.length == 1 # which can only be the last one
+ prev = path.last
+# tag "GEN: moveTo"
+ qpath.moveTo(path[0].x, path[0].y)
+ first = true
+ path.each do |vertex|
+ if first
+ first = false
+ else
+ connectToPrev qpath, vertex, prev
+ end
+ prev = vertex
+ end
+ # at this point 'prev' has become the last vertex.
+ unless prev.endpoint?
+ # this may create a lineTo which would also be done by closeSubpath
+ connectToPrev(qpath, path[0], prev)
+ # experiment: is closeSubpath really necessary??
+# closeSubpath
+ end
+ end
+ qpath
+ end
+
+ def moveTo x, y, kind = :sharp
+ if @currentpath.length == 1
+ @currentpath[0].setPosAndKind(x, y, kind)
+ else
+ @currentpath[0].type = :start
+ @currentpath.last.type = :end
+ @paths << (@currentpath = [Vertex.new(x, y, kind)])
+ end
+ end
+
+ def lineTo x, y
+# tag "lineTo #{x}, #{y}, extending currentpath: #{@currentpath.inspect}"
+ @currentpath << Vertex.new(x, y)
+ end
+
+ def closeSubpath
+# tag "closeSubpath, #currentpath= #{@currentpath.length}"
+ @paths << (@currentpath = [Vertex.new]) if @currentpath.length > 1
+ end
+
+ def smoothTo x, y
+ @currentpath << Vertex.new(x, y, :smooth)
+ end
+
+ def each_vertex
+ return to_enum(:each_vertex) unless block_given?
+ @paths.each do |path|
+ return if path.length == 1
+ path.each do |vertex|
+ # tag "el.vertex = (#{el.x}, #{el.y})"
+ yield(vertex) unless vertex.endpoint?
+ end
+ end
+ end
+
+ def firstnode?
+ @currentpath.length == 1
+ end
+
+ def [](index)
+ @paths.each do |path|
+ path.each do |vertex|
+ # tag "el.vertex = (#{el.x}, #{el.y})"
+ return vertex if (index -= 1) < 0
+ end
+ end
+ end
+
+ attr_accessor :tension
+
+ end # class PathBuilder
+
+ private # PathItem methods
+ def initialize parent, qtc
+ super
+ @path = PathBuilder.new(self)
+ end
+
+ def tension value = nil, &block
+ case value
+ when nil, Hash, Proc then DynamicAttribute.new(self, :tension, Float).setup(value, &block)
+ else self.tension = value
+ end
+ end
+
+ def tension= value
+ unless @path.tension == value
+ @path.tension = value
+ assignQPath
+ end
+ end
+
+ def closeSubpath
+ @path.closeSubpath
+ end
+
+ # if you use two lines, it's the same as one. Or you must close
+ # the first one explicitely
+ def line *args
+ first = true
+ args.each_slice(2) do |x, y|
+ return closeSubpath if x == :close
+ if @path.firstnode? && first
+ tag "firstnode, moveTo..."
+ @path.moveTo(x, y)
+ first = false
+ else
+ @path.lineTo(x, y)
+ end
+ end
+ end
+
+ def vertex x, y
+ if @path.firstnode?
+ @path.moveTo(x, y)
+ else
+ @path.lineTo(x, y)
+ end
+ end
+
+ # slightly problematic...
+ # we must delay a single node (prov. we use cubics, with quads it cannot be done)
+ # This may turn ugly in which case ActivePath has a solution.
+ # build an internal path first, then convert it to Qt::PainterPath.
+ # and we have full control.
+ # if the path was not closed the first node will be smooth.
+ def smooth *args
+ first = true
+ args.each_slice(2) do |x, y|
+ return closeSubpath if x == :close
+ if @path.firstnode? && first
+ @path.moveTo(x, y, :smooth)
+ first = false
+ else
+ @path.smoothTo(x, y)
+ end
+ end
+ end
+
+ # called by postSetup, should only be called once
+ def assignQPath
+ @qtc.path = @path.build
+# tag "#{@qpath.each_vertex { |v| v.createDebugController(@qtc) }}"
+ end
+
+ public
+
+ def postSetup
+ super
+ assignQPath
+ end
+
+ def self.new_qt_implementor qt_implementor_class, parent, qt_parent
+ res = qt_implementor_class.new
+ res.pen, res.brush = parent.pen, parent.brush
+ res
+ end
+
+ end # PathItem
+
+ createInstantiator File.basename(__FILE__, '.rb'), Qt::GraphicsPathItem, PathItem
+# tag "test for Scene#circle"
+# raise ReformError, 'oh no' unless Scene.private_method_defined?(:circle)
+
+end # Reform
View
24 lib/reform/graphics/painterpath.rb
@@ -38,6 +38,30 @@ def addTo parent, hash, &block
parent.addGraphicsItem @qtc.toFillPolygon, hash, &block
end
+ # the block is passed two arguments, the element and if a curve
+ # the element info.
+ # Unfortunately we cannot use it to make alterations, since
+ # the internal structure of the curve-info is unknown, nor is
+ # there a way to replay the curve-elements on another painterpath.
+ # Note that for straight lines such replay is very well possible.
+ def each &block
+ return to_enum unless block
+ i, n = 0, @qtc.elementCount
+ while i < n
+ el = @qtc.elementAt(i)
+ if el.curveTo?
+ i += 1
+ info = @qtc.elementAt(i)
+ yield el, info
+ else
+ yield el, nil
+ end
+ i += 1
+ end
+ end
+
+ alias :each_element :each
+
# def self.contextsToUse
# GraphicContext
# end
View
16 lib/reform/graphicsitem.rb
@@ -20,7 +20,7 @@ def position tx = nil, ty = nil
def movable onoff = nil, &block
case onoff
- when Hash, Proc then DynamicAttribute.new(self, :movable, onoff, &block)
+ when Hash, Proc then DynamicAttribute.new(self, :movable, TrueClass, onoff, &block)
else @qtc.setFlag Qt::GraphicsItem::ItemIsMovable, onoff
end
end
@@ -31,8 +31,8 @@ def movable onoff = nil, &block
def geometry x = nil, y = nil, w = nil, h = nil, &block
return @qtc.geometry unless x || w || block
case x
- when nil then DynamicAttribute.new(self, :geometryF).setup(nil, &block)
- when Hash, Proc then DynamicAttribute.new(self, :geometryF).setup(x, &block)
+ when nil then DynamicAttribute.new(self, :geometryF, Qt::RectF).setup(nil, &block)
+ when Hash, Proc then DynamicAttribute.new(self, :geometryF, Qt::RectF).setup(x, &block)
else self.geometry = x, y, w, h
end
end
@@ -56,9 +56,9 @@ def fill brush = nil, g = nil, b = nil, a = nil, &block
return @qtc.brush unless brush || block
case brush
when Symbol then @qtc.brush = frame_ex.registeredBrush(brush) || make_brush(brush, g, b, a)
- when Hash, Proc then DynamicAttribute.new(self, :brush).setup(brush, &block)
+ when Hash, Proc then DynamicAttribute.new(self, :brush, Qt::Brush).setup(brush, &block)
when Qt::Brush then @qtc.brush = brush
- when nil then DynamicAttribute.new(self, :brush).setup(&block)
+ when nil then DynamicAttribute.new(self, :brush, Qt::Brush).setup(&block)
else @qtc.brush = make_brush(brush, g, b, a)
end
end
@@ -67,9 +67,11 @@ def stroke pen = nil, g = nil, b = nil, a = nil, &block
return @qtc.pen unless pen || block
case pen
when Symbol then @qtc.pen = frame_ex.registeredPen(pen) || make_pen(pen, g, b, a)
- when Hash, Proc then DynamicAttribute.new(self, :pen, pen, &block)
+ when Hash, Proc
+ tag "#{self}::stroke + Hash/Proc -> DynamicAttribute"
+ DynamicAttribute.new(self, :pen, Qt::Pen, pen, &block)
when Qt::Pen then @qtc.pen = pen
- when nil then PenRef.new(self).setup(&block)
+ when nil then Pen.new(self).setup(&block)
else
# tag "stroke #{pen.inspect}"
@qtc.pen = make_pen(pen, g, b, a)
View
6 lib/reform/widget.rb
@@ -137,11 +137,11 @@ def contextMenu *quickyhash, &initblock
# notice: these can no longer be queried....
def enabler value = nil, &block
- DynamicAttribute.new(self, :enabled, value, &block)
+ DynamicAttribute.new(self, :enabled, TrueClass, value, &block)
end
def disabler value = nil, &block
- DynamicAttribute.new(self, :disabled, value, &block)
+ DynamicAttribute.new(self, :disabled, TrueClass, value, &block)
end
public
@@ -204,7 +204,7 @@ def windowTitle title_or_hash = nil, &block
case title_or_hash
when String then @qtc.windowTitle = title_or_hash
else
- DynamicAttribute.new(self, :windowTitle, title_or_hash, &block)
+ DynamicAttribute.new(self, :windowTitle, String, title_or_hash, &block)
end
end
View
21 lib/reform/widgets/canvas.rb
@@ -88,6 +88,7 @@ def scene id = nil, &block
def infused_scene!
scene
+# tag "infused_scene! -> #@infused_scene"
@infused_scene
end
@@ -104,7 +105,9 @@ def parent_qtc_to_use_for control
# def addGraphicsItem control, quickyhash = nil, &block
# scene.addGraphicsItem(control, quickyhash, &block)
# end
- def_delegators :infused_scene!, :addGraphicsItem, :pen, :brush, :stroke, :fill, :registeredBrush,
+
+ # NOTE: this methods must be public!!
+ def_delegators :infused_scene!, :addGraphicsItem, :registeredBrush,
:registeredPen, :area, :addAnimation, :addState
def calc_matrix
@@ -112,7 +115,7 @@ def calc_matrix
@@i ||= Qt::Transform.new
@@i.reset
@@i.rotate(@rotation) if @rotation
- @@i.scale(@scale) if @scale
+ @@i.scale(@scale, @scale) if @scale
@@i.translate(*@translation) if @translation
@@i
end
@@ -131,6 +134,14 @@ def scrollBarPolicy hor, ver = nil
public
+ def_delegators :infused_scene!, :brush, :stroke, :fill #, :pen
+
+ # FIXME this is a delegator too:
+ def pen *args, &block
+# tag "calling #{infused_scene!}.pen()"
+ infused_scene!.pen(*args, &block)
+ end
+
# rotate clockwise around center of the canvas.
def rotate deg
self.rotation = @rotation + deg
@@ -142,6 +153,12 @@ def rotation= deg
@qtc.transform = calc_matrix
end
+ def scale value = nil
+ return @scale || 1.0 unless value
+ @scale = value
+ @qtc.transform = calc_matrix
+ end
+
def rotation deg = nil
return @rotation || 0 if deg.nil?
self.rotation = deg
View
167 lib/reform/widgets/scene.rb
@@ -29,81 +29,11 @@ class Scene < Frame
require_relative '../graphical'
# note that ControlContext is already included in Frame.
include Graphical, GraphicContext, AnimationContext, StateContext
- private
-
- def initialize parent, qtc
- super
- @brushes = {} # indexed by name
- @groups = {} # ""
- @pen = defaultPen
- @brush = defaultBrush
- end
-
- # set the topleft and size of the scene. These can be floats and
- # can be freely chosen. Zoom, offset and aspectratio can be changed
- # for a specific view (and even rotation etc)
- def area x, y, w, h = w
-# tag "sceneRect := #{x}, #{y}, #{w}x#{h}"
- @qtc.setSceneRect x, y, w, h
- end
-
- # same as area 0, 0, w, h
- def size w, h = w
- @qtc.setSceneRect 0, 0, w, h
- end
-
- def indexMethod m
- @qtc.itemIndexMethod = m
- end
-
- def background brush
- @qtc.backgroundBrush = make_brush(brush)
- end
-
-=begin rdoc
- specific for scenes. This is a matrix operator. You can specify
- rotate, translate, scale, fillhue and strokehue currently.
- All other components added to the duplicate (which is a Scene)
- are added to the parent instead, after which the transformation is
- applied and we repeat the addition.
- When done the original scene parameters are restored.
- The number of times to do this is set by 'count'. If 'count' is
- not set, then 'rotate' must be set to > zero and it will be applied
- until we arrive at 1.0 or 360 (float or int param).
-=end
-# def duplicate &block
-# end AARGH
-
- class Brush < Control
- include Graphical
- private
- def initialize
- super(nil, Qt::Brush.new)
- end
-
- public
- def name aName = nil # not supported since not a Qt::Object at all. And we already index them too
- end
-
- end # class Brush
-
- class Pen < Control
- include Graphical
- def initialize
- super(nil, Qt::Pen.new)
- end
-
- public
- def name aName = nil
- end
- end # class Pen
# you can build a brush, pen and graphicitem pool.
class DefinitionsBlock < Control
include Graphical
- private
-
class GroupMacro < Control
include Graphical, SceneFrameMacroContext
private
@@ -120,29 +50,32 @@ def exec receiver, quicky, &block
executeMacros(receiver)
end
- end # GroupMacro
+ end # class GroupMacro
+
+ private # DefinitionsBlock methods
+
+ def shapegroup quickyhash = nil, &block
+ GroupMacro.new(quickyhash, &block)
+ end
# I see a pattern here (FIXME)
- def brush quickyhash = nil, &block
- # tag "Is this even called??"
- Brush.new.setup(quickyhash, &block)
+ def brush *args, &block
+ make_brush(*args, &block)
end
alias :fill :brush
- def pen quickyhash = nil, &block
- Pen.new.setup(quickyhash, &block)
+ def pen *args, &block
+# tag "Scene:: pen"
+ make_pen(*args, &block)
end
alias :stroke :pen
- def shapegroup quickyhash = nil, &block
- GroupMacro.new(quickyhash, &block)
- end
-
- public
+ public #DefinitionsBlock methods
def method_missing sym, *args, &block
+ tag "#{self}::method_missing(:#{sym})"
if args.length == 1 && !block
# tag "single arg: #{self}.#{sym}(#{args[0]})"
case what = args[0]
@@ -162,11 +95,57 @@ def method_missing sym, *args, &block
# end
end # class DefinitionsBlock
+
+ private # Scene Methods
+
+ def initialize parent, qtc
+ super
+ @brushes = {} # indexed by name
+ @groups = {} # ""
+ @pen = defaultPen
+ @brush = defaultBrush
+ end
+
+ # set the topleft and size of the scene. These can be floats and
+ # can be freely chosen. Zoom, offset and aspectratio can be changed
+ # for a specific view (and even rotation etc)
+ def area x, y, w, h = w
+# tag "sceneRect := #{x}, #{y}, #{w}x#{h}"
+ @qtc.setSceneRect x, y, w, h
+ end
+
+ # same as area 0, 0, w, h
+ def size w, h = w
+ @qtc.setSceneRect 0, 0, w, h
+ end
+
+ def indexMethod m
+ @qtc.itemIndexMethod = m
+ end
+
+ def background brush
+ @qtc.backgroundBrush = make_brush(brush)
+ end
+
+=begin
+ specific for scenes. This is a matrix operator. You can specify
+ rotate, translate, scale, fillhue and strokehue currently.
+ All other components added to the duplicate (which is a Scene)
+ are added to the parent instead, after which the transformation is
+ applied and we repeat the addition.
+ When done the original scene parameters are restored.
+ The number of times to do this is set by 'count'. If 'count' is
+ not set, then 'rotate' must be set to > zero and it will be applied
+ until we arrive at 1.0 or 360 (float or int param).
+=end
+# def duplicate &block
+# end AARGH
+
def define quickyhash = nil, &block
DefinitionsBlock.new(self).setup(quickyhash, &block)
end
- public
+ public # Scene methods
def registerBrush name, brush
case brush
@@ -193,26 +172,16 @@ def registeredBrush(name)
# end
# Set the default fill for elements.
- def fill brush = nil
- return (@brush || defaultBrush) unless brush
- @brush = case brush
- when Symbol
-# tag "is #{brush} registered? -> #{@brushes[brush]}"
- @brushes[brush] || color2brush(brush)
- when Qt::Brush then brush # trivial speed-up case
- else make_brush(brush)
- end
+ def fill *args, &block
+ return @brush unless !args.empty? || block
+ @brush = make_brush(*args, &block)
end
# Set the default stroke for elements
- def stroke pen = nil
- return (@pen || defaultPen) unless pen
- @pen = case pen
- when Symbol
- @pens[pen] || color2pen(pen)
- when Qt::Pen then pen
- else make_pen(pen)
- end
+ def stroke *args, &block
+ return @pen unless !args.empty? || block
+ @pen = make_pen(*args, &block)
+# tag "make_pen -> #{@pen.inspect}"}
end
alias :brush :fill
View
11 qtruby4631.patch
@@ -0,0 +1,11 @@
+--- /usr/lib/ruby/gems/1.9.1/gems/qtbindings-4.6.3.1/lib/Qt/qtruby4.rb 2010-10-25 22:03:56.136190328 +0200
++++ /usr/lib/ruby/gems/1.9.1/gems/qtbindings-4.6.3.1/lib/Qt/qtruby4.rb 2010-10-25 22:10:29.745867254 +0200
+@@ -2774,7 +2774,7 @@
+ # Multiple matches are an error; the equality test below _cannot_ be commented out.
+ # If ambiguous matches occur the problem must be fixed be adjusting the relative
+ # ranking of the arg types involved in checkarg().
+- elsif current_match == best_match && id.smoke == chosen.smoke
++ elsif current_match == best_match && chosen && id.smoke == chosen.smoke
+ puts "multiple methods matching, this is an error" if debug_level >= DebugLevel::Minimal
+ chosen = nil
+ end
Please sign in to comment.
Something went wrong with that request. Please try again.