HTML like layouting - python

I'm trying to implement my own little flow-based layout engine. It should imitate the behavior of HTML layouting, but only the render-tree, not the DOM part. The base class for elements in the render-tree is the Node class. It has:
A link to the element in the DOM (for the ones that build a render-tree with that library)
A reference to it's parent (which is a ContainerNode instance or None, see later)
A reference to the layouting-options
X, Y, width and height (the position is computed in layout(), after the size has been computed in compute_size(). While the position is defined by the layout() method of the parent, the size is defined by the options reference, for instance).
It's methods are:
reflow() invoking compute_size() and layout()
compute_size() that is intended to compute the width and height of the node.
layout() which is intended to position the sub-nodes of the node, not the node itself.
paint() which is there to be overwritten by the user of the library.
The ContainerNode class is implementing the handling of sub-nodes. It provides a new method called add_node(), which adds the passed node to the containers children. The function also accepts a parameter force which defaults to False, because the container is allowed to deny the passed node, except force is set to True.
These two classes do not implement any layouting algorithm. My aim was to create different classes for the different types of layouts (In CSS, mainly defined by the display attribute). I did some tests with text-layouting last night and you can find my code from at pastebin.com (requires pygame). You can save it to a python script file and invoke it like this:
python text_test block -c -f "Georgia" -s 15
Note: The code is really really crappy. I appreciate comments on deep lying misconceptions.
The class InlineNodeRow from the code mentioned above actually represents my idea of how to implement the node that lays out similar to the display:inline attribute (in combination with the NodeBox).
Problem 1 - Margin & Padding for inline-text
Back to my current approach in the library: A single word from a text would also be represented as a single node (just like in the code above). But I noticed two things about margins and paddings in a <span> tag.
When margin is set, only horizontal margin is taken in account, the vertical margin is ignored.
The padding is overflowing the parent container and does not "move" the span node.
See http://jsfiddle.net/CeRkT/1/.
I see the problem here: When I want to compute the size of the InlineNodeBox, I ask a text-node for it's size and add it to the size of the node. But the text-nodes size is including it's margin and padding, which is not included in the HTML renderer's positioning. Therefore the following code would not be right:
def compute_size(self):
# Propagates the computation to the child-nodes.
super(InlineNodeBox, self).compute_size()
self.w = 0
self.h = 0
for node in self.nodes:
self.w += node.w
if self.h < node.h:
self.h = node.h
node.w would include the margin and padding. Next problem I see is, that I for laying out the text-nodes correctly, I wanted to split them into single TextNodes for each word, but the margin and padding would then be applied to all these nodes, while the margin and padding in HTML is to the <span> tag only.
I think my current idea of putting each word into a seperate node is not ideal. How to browsers structure their render-tree, or do you have a better idea?
Problem 2 - Word too long, put it into the next line.
The InlineNodeBox class currently only organizes a single line. In the code example above, I've created a new InlineNodeBox from within the NodeBox when the former refused to accept the node (which means it didn't fit in). I can not to this with my current approach, as I do not want to rebuild the render-tree all over again. When a node was accepted once, but exceeds the InlineNodeBox on the next reflow, how do I properly manage to put the word into the next line (assuming I keep the idea of the InlineNodeBox class only organizing a single line of nodes)?
I really hope this all makes sense. Feel free to ask if you do not understand my concept. I'm also very open to criticism and ideas for other concepts, links to resources, documentations, publications and alike.

Problem 2:
You can do it like HTML renderers do and render a multiline (e.g. check if the new word to be added exceeds the width and add a new line if it does). You can do it in your InlineNodeRow, by taking care of height too and wrapping words if they exceed the max width.
Problem 1:
If you do figure out problem 2 for text, then you can put in the offset (horizontal padding) only for the first line.
Although <span> doesn't take height into consideration, it does take line-height, so your calculation could be that the default height is the font height unless you have a line-height option available.
Mind you, if you have two or more successive InlineNodeRow representing spans, you'd need some smart logic to make the second one continue from where the first one ended :)
As a side note, From what I remember from Qt's rich text label, each set of words with the same rendering properties is considered to be a node, and its render function takes care of calculating all the stuff. Your approach is a bit more granular and its only disadvantage from what I see is that you can't split words.
HTH,

May have found solution to problem 1 in the box model documentation (you may want to check out the documentation about clearance and the one for overflow as well for problem 2).
"margins of absolutely positioned boxes do not collapse."
You can see this jsfiddle for an example.

Related

Python Kivy, need help to understanding a strange technique. self._object.clear() etc

I feel guilty for asking question I can not properly name because I can not name the pattern used in the code.
There is a code on github I'm trying to understand and failing to do so.
https://github.com/kivy-garden/speedmeter/blob/master/kivy_garden/speedmeter/init.py
Pattern I don't understand is in lines 128,129 and 181, 182 and many other places.
Big picture is.
There a class
class MyClassName(Widget):
there are methods e.g.
def _draw_this_and_that(self):
self._someName.clear()
add = self._someName.add
add(Color(rgba=get_color_from_hex(color)))
This "_someName" found in whole code only in those 2 places as my code sample.
I understand that
add = self._someName.add
creates function "add" but why that is needed? why not calling
self._someName.add
instead?
I guess that
self._someName.clear()
does erase whatever was added to "_someName", right?
I completely do not understand how
add(Color(rgba=get_color_from_hex(color)))
does it job (but it does) and then whatever is drawn will be with that color.
Do I guess it right that if I need to change color (if some condition met) then I could just add different color?
add(Color(rgba=get_color_from_hex(different_color)))
and don't stress that adding will cause memory leak because
self._someName.clear()
will take care of it?
I never seen such pattern. I'd be very happy if someone could explain how it works and why.
Thank you in advance!
The _somenameIG are canvas instruction groups that are created in the __init__() method:
add = self.canvas.add
for instructionGroupName in _ig:
ig = InstructionGroup()
setattr(self, '_%sIG' % instructionGroupName, ig)
add(ig)
So, the self._someName.clear() is clearing a canvas instruction group, and the add() method adds instructions to the group.
In addition to the other answer I would like to address the following:
Pattern I don't understand is in lines 128,129 and...
According to its documentation the attr. sectors in line 130 is a list of alternating values and colors a copy of which is stored locally by l and then the color value(s) is(are) passed to the InstructionGroup. Since it is used as a redrawing mechanism, the instruction groups are removed from canvas by the method clear before drawing again.
creates function "add" but why that is needed? why not calling...
This is done mainly to prevent the re-evaluation process.

why `view.scope_name(view.sel()[0].b)` is diffrent for the same doc?

I need to get the scope when I create a *.sublime-snippet.The command view.scope_name(view.sel()[0].b) is what I stumbled across for the ST2 console that conveniently outputs the scope name of the currently-selected tab.
I tested it in the ST console several times, however, it output the different result.
I just want to know why this happens?(BTW, I use mac)
And I also curious that what is view.sel()[0].b mean? What's mean of the arg point of the scope_name method?(the officail API Reference demonstrates scope_name(point) means the syntax scope name assigned to the character at the given point.)
If you need to know a scope for any reason (including creating a snippet), by far the simplest solution is to select Tools > Developer > Show Scope Name from the menu (see the menu item to see what the default key binding for your platform is). This will display a popup that shows you the full scope at the current caret location in the document. Using this method you don't need to memorize any API calls.
I tested it in the ST console several times, however, it output the different result.
I just want to know why this happens?
In a nutshell, every file has a syntax associated with it (based on extension, you specifically telling Sublime what to use, etc), and that syntax scans the entire file applying one or more scopes to each character in the document. The scopes of a character determines how the color scheme you're using colors the text.
As you will see when you perform the above menu command, there can be many scopes that apply to any given cursor location; the farther along the list of scopes a scope is, the more specific the scope becomes. So as you move the cursor through the document, the specificity of that particular character may change, but the first part of the scope remains constant.
As such, unless you want to trigger a snippet only in a really, really specific location (e.g. "only in front of a numeric constant that is a function argument in a C# class method) you generally only need the first part of the scope, i.e. source.js or text.html.
And I also curious that what is view.sel()[0].b mean?
In Sublime, every tab is represented by a view object, which is an object by which all interaction with Sublime for that particular file takes place. For example, editing commands need to be executed on a particular view so that Sublime knows what file you're editing.
Inside of every view there are one or more cursors for editing, and each one of those cursors represents one selected area. This is true even if there is visibly no text selected, in which case the selection has a length of 0.
The call view.sel() returns back to you a list of all of the selections that exist inside of that view, with the subscript [0] indicating "the first selection". So view.sel()[0] means "Give me a reference to the first selection (cursor) in this view".
Every selection is represented by a Region object, which is an object that contains two Points, a and b; a is the point at which the selection starts, and b is the point at which the selection ends. In the case of a selection that is empty (i.e. just a blinking cursor), a and b are the same value.
It is possible for b to be smaller than a, which means that you selected text from right to left instead of left to right. In effect, b tells you the point in the selection that the cursor is currently sitting on.
Hence, view.sel()[0].b means "Get me a reference to the first selection in this view, and then tell me the point that the cursor in that selection is sitting at".
What's mean of the arg point of the scope_name method?(the officail API Reference demonstrates scope_name(point) means the syntax scope name assigned to the character at the given point.)
As you may be able to tell from the above, Point represents a single location in a document. The first character in a file is at Point 0 and the last character in the file is at point view.size() (a method which tells you the number of characters in the current file).
Since the scope of every character in the file is potentially different, the method scope_name needs to know what part of the file you want to know the scope for, so it requires you to tell it what position you're asking about.

pygtk: overriding gtk.Paned.compute_position

I have a HPaned that one of its children changes its size requirement frequently, since its text changes. The result is that the pane moves every time the text is changed. I'd like to override the Paned.compute_position method so that the size of the child will not decrease, just increase. However, I can't find a way to override it. Even
class MyHPaned(gtk.HPaned):
def do_compute_position(self, allocation, child1_req, child2_req):
print "Hi"
return gtk.HPaned.compute_position(self, allocation, child1_req, child2_req)
gobject.type_register(MyHPaned)
doesn't work. Do you have a suggestion? Thanks!
Overriding gtk_paned_compute_position is not possible, since that function is not virtual in GTK itself. Also, gtk_paned_compute_position is marked as internal and deprecated and is not called anywhere from GTK+-2.24.x sources. I suspect it was only exported so that you could find out the position of the separator, not to affect it through overriding.
Instead of attempting to override HPaned.compute_position, you should place into the paned a single-child container (e.g. a child of gtk.Bin) that implements the desired resizing policy by hooking into the size-allocate signal and calling set_size_request with the desired size. This will be automatically respected by HPaned.

Recognize which model object is clicked in Tkinter

I have many overlapping shapes representing irrelevant background items on a canvas. I also have a pattern of non-overlapping circles, each of which is a "hole". Each "hole" sprite (circle) has an associated "hole" object, though never explicitly in the code. (side note: I would love to have a logical association between model and view with these objects, but haven't found a smart way to do that). Each "hole" is different, and has different effects.
There is a small circular "ball" which can be dragged into any "hole". I found how to drag and drop from this question. I need to find which hole the ball went into.
The best way I have found to do that so far is to:
create a dict mapping the coordinates of the center of the hole sprite to the hole object
tag each hole like this:
t=("hole", "hole_at_{}_{}".format(x, y))
on releasing the ball, do this:
def on_ball_release(self, event):
'''Process button event when user releases mouse holding ball.'''
# use small invisible rectangle and find all overlapping items
items = self._canvas.find_overlapping(event.x - 10, event.y - 10, event.x + 10, event.y + 10)
for item in items:
# there should only be 1 overlapping hole
if "hole" in self._canvas.gettags(item):
# get the coordinates from the tag
coords = tuple([int(i) for i in self._canvas.gettags(item)[1].replace("hole_at_", "").split("_")])
# get associated object using dictionary established before
hole = self._hole_dict[coords]
hole.process_ball()
return
That seems very messy. I feel there should be some smarter way to do this.
Disclaimer: I don't use Python, but many Tkinter questions can be answered in a useful from an experience with Tcl/Tk, which I have. In this case, it takes some more work to figure out whether what I would do in Tcl is easy to represent with Tkinter.
First, I wouldn't add "identifier tags" (hole_at_...): if I have model objects corresponding to canvas items, I would use the item id (which canvas returns during item creation) as an index, to be able to find an object for an item id without parsing tags. (And if I had to add string identifiers, even if I decided to make them from coordinates, I would use that very string as my dictionary key, to avoid reparsing it. Do we need coordinates later? Then make them properties of the hole object).
Second, I would use pathName find subcommand with multiple criteria to find (canvas id of) item which is tagged as hole and is nearest to the given point (overlapping is fine when we want to ignore drops too far from any hole, closest is for the case where nearest hole should be used even if it's not too near). Here is the problematic part: does Tkinter support multiple criteria in canvases' $pathName find?

How to apply a modifier in Python, creating a new mesh?

Let's say I have a bpy.types.Object containing a bpy.types.Mesh data field; how can I apply one of the modifiers associated with the object, in order to obtain a NEW bpy.types.Mesh, possibly contained within a NEW bpy.types.Object, thus leaving the original scene unchaged?
I'm interested in applying the EdgeSplit modifier right before exporting vertex data to my custom format; the reason why I want to do this is to have Blender automatically and transparently duplicate the vertices shared by two faces with very different orientations.
I suppose you're using the 2.6 API.
bpy.ops.object.modifier_apply (modifier='EdgeSplit')
...applies to the currently active object its Edge Split modifier. Note that it's object.modifier_apply (...)
You can use
bpy.context.scene.objects.active = my_object
to set the active object. Note that it's objects.active.
Also, check the modifier_apply docs. Lot's of stuff you can only do with bpy.ops.*.
EDIT: Just saw you need a new (presumably temporary) mesh object. Just do
bpy.ops.object.duplicate()
after you set the active object and the new active object then becomes the duplicate (it retains any added modifier; if it was an object named 'Cube', it duplicates it, makes it active and names it 'Cube.001') to which you can then apply the modifier. Hope this was clear enough :)
EDIT: Note, that bpy.ops.object.duplicate() uses not active object, but selected. To ensure the correct object is selected and duplicated do this
bpy.ops.object.select_all(action = 'DESELECT')
object.select = True
There is another way, which seems better suited for custom exporters: Call the to_mesh method on the object you want to export. It gives you a copy of the object's mesh with all the modifiers applied. Use it like this:
mesh = your_object.to_mesh(scene = bpy.context.scene, apply_modifiers = True, settings = 'PREVIEW')
Then use the returned mesh to write any data you need into your custom format. The original object (including it's data) will stay unchanged and the returned mesh can be discarded after the export is finished.
Check the Blender Python API Docs for more info.
There is one possible issue with this method. I'm not sure you can use it to apply only one specific modifier, if you have more than one defined. It seems to apply all of them, so it might not be useful in your case.

Categories