adventures in types
Hi, this is a brief description of the stuff I've been lately polluting the mailing list with: the new decoration framework, this floating layout and so on. I'm going to be long. Sorry, but I though that such a digression could be useful for non/wannabe developers too. So, it all started with "doLayout": doLayout :: layout a -> Rectangle -> Stack a -> X ([(a, Rectangle)], Maybe (layout a)) take the screen geometry, an ordered stack of windows and produce a list of those widows coupled with their generated geometry and, possibly, an updated version of the layout - the way of doing the same operation the *next* time. Cool. This is what a window manager is all about, after all... But soon we discovered that manipulating a list of windows and their geometry, when you have the screen geometry and the ordered stack, may be more fun. You have more information, after all. This is how layout modifier started to appear. We stared seen layouts doing: (wrs,ml) <- doLayout l r s and then you start playing with "wrs"...;) When we switched to the class approach, David Roundy (my mentor when it comes to type classes and stuff like that, even though I have the feeling this has been a type adventure for him too...[1]), came up with a nice solution: a layout (ModifiedLayout) which could take a layout modifier and produce a ... modified layout! The layout modifier must just be an instance of the LayoutModifier class, with safe default method implementations. The basic method is "redoLayout": redoLayout :: m a -> Rectangle -> Stack a -> [(a, Rectangle)] -> X ([(a, Rectangle)], Maybe (m a)) As you see, this is like a "doLayout", but it gets the already produced list of windows and their respective geometry. You have the whole workspace populated of windows, and you can manipulate them! So people started writing layout modifiers with this new framework. But sometimes you can still see layout modifiers that are implemented as layouts, that is to say, as member of the LayoutClass: Mirror, Reflect, AvoidStruts just to mention a few. Why? In most of the cases this happens just because of the need to modify the rectangle or the stack to be passed to the "doLayout" with the layout to be modified. Unfortunately the LayoutModifier class doesn't (well, didn't) have something to deal with this situation. So I created a new method class, "modifyLayout": modifyLayout :: (LayoutClass l a) => m a -> l a -> Rectangle -> Stack a -> X ([(a, Rectangle)], Maybe (l a)) Now you can access the layout you are modifying and you can change the rectangle and the stack. Then you have the generated list of windows and geometries to start playing with. And, for free, you get all the safe methods for correctly passing messages around, for dealing with empty workspaces, and so on. Now we can rewrite Reflect as a layout modifier: Sun Jan 27 17:58:54 CET 2008 Andrea Rossato <andrea.rossato@unibz.it> * Reflect: reimplemented as a layout modifier (which makes it compatible with windowArranger and decoration) M ./XMonad/Layout/Reflect.hs -15 +11 The same with AvoidStruts: Sun Jan 27 15:43:01 CET 2008 Andrea Rossato <andrea.rossato@unibz.it> * ManageDocks: implement AvoidStruts as a layout modifier M ./XMonad/Hooks/ManageDocks.hs -17 +11 You see? The code gets shorter! And the source of many bugs gets eliminated! I've also add the pure versions of the redoLayout and the message handling methods: this way you can start manipulating the list of (windows,geometries) purely... Don, do you like that possibility?? I'm sure you do! ;-) Ok, so, what about this New Decoration Framework I've been talking about? After this small type class cleanup (well it did not occur in this order actually, it's an adventure, after all...;), I started thinking about a floating layout. My first intent was to have a way of manipulating the windows' geometry with the keyboard. This is how WindowArranger started. The idea is to have a layer, a layout modifier, with a list of windows whose geometry is managed by the underlying layout. But when a window's geometry is modified by a message sent by the user, its geometry will not be managed by that layout anymore, but only by the WindowArranger modifier itself. This is done with the ArrangedWindow type, with 2 constructors "WR (a,Rectangle)" for the windows managed by the underlying layout and "AWR (a,Rectangle)" for windows whose geometry has been modified by the user. This list is processed with pure code only! And WindowArranger was the fist pure layout modifier written. I then decided to rewrite Tabbed too. Once someone asked to have tabs with other layouts too. This is how I got the idea of transforming the Tabbed layout in a Tabbed layout modifier. Decorating a window only means creating a second window with a given geometry and inserting it in the window list before the window we are decorating. We need to keep track of all that, as we did with tabbed, but that can be done with pure code only, as the WindowArranger demonstrated. So I rewrote tabbed in a more general way, as an instance of the LayoutModifier, and to be used with ModifiedLayout. I didn't need to write anything to communicate with the X server. I just used XMonad.Util.XUtils. I just needed to write the pure core to process the state for keeping track of the decorations we created, and the pure code for inserting them in the (window,geometry) list to be passed to xmonad for actually refreshing the screen. And I obviously copied my mentor's (David Roundy) approach. I created a class, DecorationStyle, which basic method is "decorate": decorate :: ds a -> Dimension -> Dimension -> Rectangle -> W.Stack a -> [(a,Rectangle)] -> (a,Rectangle) -> X (Maybe Rectangle) a decoration style, the user configured width and height of the decoration, the screen and the stack, the windows with their geometry, and the specific window (and its geometry) you are creating the decoration for. If you return Just a geometry, that geometry will be used to create the decoration (with the user supplied configuration), otherwise no decoration is created for that window. Obviously there are pure methods here too. This is XMonad.Layout.Decoration: a layout modifier that takes a member of the DecorationStyle class and a shrinker to generate the geometry of a decoration to be passed to xmonad.. So I then created some decoration styles: the default one DefaultStyle, a tabbed like decoration (XMonad.Layout.Tabbed), a very basic SimpleDecoration (Xmonad.LayoutSimpleDecoration), and what I was told was the old dwm like decoration style (XMonad.Layou.DwmStyle). Together with these decoration styles there are some simple layoutModifier to apply those decoration to any layout. Remember that Decoration is an instance of the LayoutModifier class: - a simpleDeco modifier: simpleDeco :: Shrinker s => s -> DeConfig SimpleDecoration a -> l a -> ModifiedLayout (Decoration SimpleDecoration s) l a - a dwmStyle modifier: dwmStyle :: (Eq a, Shrinker s) => s -> DeConfig DwmStyle a -> l a -> ModifiedLayout (Decoration DwmStyle s) l a All these modifiers take a shrinker (the Shrinker class was one of David's classes), and default configuration. Then I rewritten the tabbed layout, as a layout modifier of the XMonad.Layout.Simplest layout, a pure one line layout I wrote specifically for tabbed: - a tabbed layout: tabDeco :: (Eq a, Shrinker s) => s -> DeConfig TabbedDecoration a -> ModifiedLayout (Decoration TabbedDecoration s) Simplest a (there's also a compatibility layer with the old tabbed layout) Then I added some other tools for helping combining this stuff, such as ResizeSize, a layout modifier to modify the screen geometry. That's it. Suppose you want to apply the tabbed decoration to circle without shrinking each window: decoration shrinkText defTabbedConfig (resizeVertical 20 Circle) Do you want to add a simple decoration to all your layouts, with the possibility of moving/resizing windows? simpleDeco shrinkText defaultSimpleConfig (windowArrange $ layoutHook defaultConfig) Obviously we can hide this combinations to the users, and provide them with the final stuff. Ok, so, where is the floating layout? Well, it is just a side product of this stuff. Go read it: http://gorgias.mine.nu/repos/xmonad-contrib/XMonad/Layout/SimpleFloat.hs This is its type: simpleFloat :: ModifiedLayout (Decoration SimpleDecoration DefaultShrinker) (ModifiedLayout WindowArranger SimpleFloat) a SimpleFloat is a very simple layout (of the LayoutClass) which will look at the newly created window's attributes and set its geometry accordingly. The WindowArranger is set so that it will arrange all windows after creation (which means the underlying layout will not touch their geometry anymore). The WindowArranger lets you to move/resize them. On top of that I added the a Decoration modifier with a SimpleDecoration style and the default shrinker. simpleFloat' will let you provide a custom configuration and a custom shrinker. To support this stuff the only required change to the core (Spencer pushed it yesterday) is to add "emptyLayout" to the LayoutClass, so that a layout is called even when the workspace is empty. This has many beneficial effects. ShowWName, for instance, can show the names of empty layouts too. The mouse UI should go on to of Decoration. But there's one side affect: a layout modifier *cannot* call "focus w". I still don't exactly understand the whys and hows, but I'm sure it cannot. I'm ready to push. My code is backward compatible. But there are those layout modifier implemented as instances of the LayoutClass. If used in the chain of layout modifiers I showed you above, if they do not implement the "emptyLayout" method correctly they will prevent Decoration from removing the last windows. I'll wait some feedback before pushing. Sorry I was so long, but I wrote quite some code. Cheers, Andrea [1] Read here :) http://www.haskell.org/pipermail/xmonad/2007-September/002148.html
participants (1)
-
Andrea Rossato