Win32 GUI scripting

Started by
5 comments, last by Oxyacetylene 18 years, 8 months ago
I've been trying to follow John Torjo's lead and build a GUI scripting language for Win32... I'd use his, but I'd have to retro-fit all our Win32 widget code, and that ain't gonna happen. Here's an example of some pseudo-script I've been tinkering with: (questions to follow)

script sample:

Layout layout1
{    
	// first of all, set the surfaces and properties
	// this is applied to the controls/dialogs when the
	// surfaces are created
	
	plain IDD_DIALOG1
	{
		plain IDC_BUTTON1
		{
			text text { val="Cancel" };
			bitmap imgbtn { filename="button.bmp" };
			bg back { img="bgimage.png"; color="#00ffff" };
		};
	};
	
	// now set sizing and position information; these
	// are to be evaluated every time a WM_SIZE event occurs
	//
	// The "size" section happens after the initialization
	// section so that all surfaces have been defined before
	// evaluating "size"
	
	size
	{
		IDD_DIALOG1
		{
		    width = parent.width / 3;
		    height = 200;
		    pos.x	= screen.width/2 - parent.pos.x/2;
		    pos.y	= screen.height/2 - parent.pos.y/2;
		    			
			IDC_BUTTON1
			{
			    pos				= parent.pos;
			    
				text.pos		= pos;
				text.width		= width/2;
				imgbtn.pos.x	= text.pos.x + text.width;
				imgbtn.width	= width/2 
			}
		};
	
	};
};

Layout layout2
{    
	plain IDD_DIALOG1
	{
		plain IDC_BUTTON1
		{
			text text { val="Cancel" };
			bitmap imgbtn { resource=IDC_BITMAP1 };
			gradient_bg back { begin_color=root.bg.color; to_color="#ffffff"; angle=270 };
		};
	};
	
	size
	{
		IDD_DIALOG1
		{			
		    width = 420;
		    height = 400;
		    pos.x	= screen.width/2 - parent.pos.x/2;
		    pos.y	= screen.height/2 - parent.pos.y/2;
		    
			IDC_BUTTON1
			{
				pos				= parent.pos;
				
				text.pos.x		= parent.pos.x + parent.width/2;
				text.width		= parent.width/3;
				imgbtn.pos.x	= text.pos.x + text.width;
				imgbtn.width	= parent.width/2 
			}
		};
	
	};
};

// layout, plain, button -- keywords
// Layout1/Layout2 -- 1 to 1 mapping with WindowLayout objcts
// text/bitmap/bg/gradient_bg -- aliases for text, bitmap,
//                               background, and gradient
//                               background surface classes.
//                               New surfaces with those
//                               properties will be created
//                               for the particular control
//                               in that particular layout
// val -- value to be given the surface (in context)...
//        meaning a "string" if the surface displays a string,
//        a filename, if the surface displays an image, etc
// name -- name of the surface, to be used in scripting
// pos -- x,y position of upper left corner of surface,
//        relative to the parent surface/ctrl. Changed
//        if surface is resized, or moved
// width/height -- width/height of surface; usually a relative
//                 computation done each time the surface
//                 is notified to update size
Q1: Doesn't this look do-able with XML? The problem I'm seeing is that XML doesn't allow you to evaluate expressions... so something like "IDC_BUTTON1.width = parent.width" couldn't work. Q2: If that's what XSL does, how? And if XSL does it... how do I get that functionality into my app? Does "TinyXML" implement XSL scripting? Q3: If I forego XML/XSL... what difficulties do you see in implementing the scripting as I've written it? Will something just plain not work? I'd probably try to parse with Boost::Spirit... I'd implement everything (likely) by creating a LayoutManager object in code, after all dialogs/widgets were created... and then parse the script... creating "surfaces" as necessary. Once all surfaces were created, I'd call LayoutManager::init() or something, to get all positions/sizes updated for the first time. I haven't written any parsers yet... I'm not sure which way to go, is really the problem... and if the scripting (as written) would be a good way to do it. Any thoughts at all... to help get me from point A (here) to point Z would be very helpful. Thanks! Chad
my_life:          nop          jmp my_life
[ Keep track of your TDD cycle using "The Death Star" ] [ Verge Video Editor Support Forums ] [ Principles of Verg-o-nomics ] [ "t00t-orials" ]
Advertisement
elaborating...

Seems like it's pretty straightforward to create an XML document describing the UI model....

<layout name="Layout1">    <plain resourceID=IDD_DIALOG1>        <plain resourceID=IDC_BUTTON1>            <text name="text">Cancel</text>            <bitmap name="imgbtn" filename="button.bmp" />            <bg name="back" img="bgimage.png" color="#00ffff" />        </plain>    </plain></layout>


...and it might even be OK to create an XSL stylesheet to transform some of the values for the initial setup of the GUI...

...but what happens when, during the course of running the app, a user resizes the app?

You can't exactly run the XSL stylesheet again...

The resizing logic should happen automatically-- I guess that's what the LayoutManager object/class is for...

So (just confirming)... this means I'll have to create a type of scripting language that can be evaluated at any time... not just during initialization-- or when the XML document is being loaded?

Ugh... what a P.I.T.A. GUI programming is

:(


Chad
my_life:          nop          jmp my_life
[ Keep track of your TDD cycle using "The Death Star" ] [ Verge Video Editor Support Forums ] [ Principles of Verg-o-nomics ] [ "t00t-orials" ]
In a few GUI toolkits I've seen (Qt, wxWidgets, etc) there contains objects used as sizers and spacers that are setup in such a way to expand thier children (buttons, text boxes, other sizers, any other widget) automatically on resize so you dont have to program in any specifics. Basically they work like containers, with rules on what to do, something like: "my children are stacked vertically, allow them to expand horizontally when resized, but only increase the space between them when the window is stretched vertically" as an example (without code) for what the rules would allow. In fact, wxWidgets can load entire forms from an XML file during runtime.

If your using the standard Win32 stuff, I haven't messed with it much, most of what I do is multiplatform (hence wx and qt), so I dont have a lot of experience with normal win32, but even if the objects dont exist, it shouldn't be too hard to create them and set up your own on_resize event, right?

(link to wx topic about sizers)

EDIT:
I realized, I forgot to point out that while most sizers in gui toolkits make the resizing stuff handled automatically, they can make arranging your gui form the biggest headache in the world if its complicated and involved. ;)
Thanks for the post, Rhalin. (rate++)

Yeah, the resizing code/algorithms aren't too hard to express... they're just hard to evaluate at runtime. Basically I'm looking for a way to load in expressions, and evaluate them at runtime.

EG:

widget1.width = widget1.parent.width - widget2.width;

The "placement" code isn't actually all that bad; you just have to decide what the relationships between each of the widgets and subwindows are in your dialog/window.

"surfaces" would be used for the drawing; they are what's actually resized/drawn... Win32 drawing code/APIs are wrapped...

Thanks again.


C
my_life:          nop          jmp my_life
[ Keep track of your TDD cycle using "The Death Star" ] [ Verge Video Editor Support Forums ] [ Principles of Verg-o-nomics ] [ "t00t-orials" ]
Ahh k. I do a lot of work with logical operations in XML. You could handle some of it with a nice recursive function

(using -1 as "default")<widget name="form" name="form1">	<size positioning="absolute">		<values x="-1" y="-1" w="100" h="100" />	</size>	<children>		<widget type="button" name="button1">			<size positioning="relative">				<values>					<height>						<adjustments>							<adj fromReference="parent" fromAtt="width" mod="/2">							</adj>						</adjustments>					</height>				</values>			</size>		</widget>	</children></widget>


In this example, the button is a child of the form, and gets its width from the parent divided by 2.

Granted, it would take a nice chunk of code to evaluate what you want, you could even write a simple XML-based math system to express what you want exactly with variables and everything.

For example...
<expression>	<left>		<var type="reference" name="parent" value="width" />			</left>	<operator type="divide" />	<right>		<expression>			<left>				<var type="widget" name="button1" attribute="width" />			</left>			<operator type="add">			<right>				<const value="2" />			</right>		</expression>	</right></expression>


Expressions contain a left side, right side, and operator. A side can contain a variable, constant, or another expression (yay recursion!). Variable types can be either a reference (parent,child,etc), a specific object (widget, named class it has access to), or named objects it knows because they're defined in the XML, such as other widgets. The XML above would represent something like:
(parent.width / (button1.width + 2))

Its actually part of an experimental project I'm working on at the moment, but have had to shelve for a bit, which implements and entire scripting language using only XML for the language syntax, variables, loops, classes, functions all done in XML. The idea being that you'd only use the language when you want something easily parseable, and could transform it into a C-like syntax very easily with things like XML-Schema. In fact, translating from a very basic syntax into the XML version should be fairly easy as well.

Anyways, I'm getting off track, but does this help any? or am I going in the wrong direction for what your looking for?
No, this does help.

In your examples, where does the actual processing take place? I assume you're parsing out the <expression> elements (for example) and then evaluating them in code somewhere...

Strangely, the parsing isn't a problem...

I've built a C++ class that uses boost::Spirit to recursively evaluate mathematical expressions

((3 + 4) * 5) % 2

and so forth.

The difficulty lies in getting from this:

button1.pos.x = parent.pos.x + 50

to this:

125 + 50

I suppose an XSLT stylesheet could calculate the initial value of "parent.pos.x"... but what happens when the code, or the user changes the parent's position? Since all the stylesheet does is transform a static XML document, how would the stylesheet have access to the updated "parent.pos.x"?

Answer: It doesn't.

That's why scripting sounds like the only good option.... or if I could get my head around how to build those expressions using nested functors, or something...

That's the one thing that would remain constant... at least on a per-layout basis: the expressions, like "button1.pos.x = parent.pos.x + 50"...

... but "parent.pos.x" can change, and the XML doc describing the initial settings does not. Editing an XML doc on the fly just sounds silly... if all it's doing is keeping track of window sizes/positions.

...

Anywho... just thinkin aloud.

Any more suggestions/discussions are greatly appreciated. Maybe we can all put our heads together and break this problem down for posterity [lol]


C
my_life:          nop          jmp my_life
[ Keep track of your TDD cycle using "The Death Star" ] [ Verge Video Editor Support Forums ] [ Principles of Verg-o-nomics ] [ "t00t-orials" ]
Quote:Original post by Verg
I'd probably try to parse with Boost::Spirit...


I'd possibly be wary of using boost::spirit for something like this. This might just be because I was using Visual Studio 2002, but when trying to write my effect parser with boost::spirit, the expansion of the template code overflowed several compiler limits, when the parser got to any kind of decent size.

As I say, it could be because I was using Visual Studio 2002, and implementing my own syntax tree system, but it's something to be wary of.

In the end, I ended up going for flex++, and bison++, which works fine.

This topic is closed to new replies.

Advertisement