Rectangular container with border

Is there a GUI component that represents a rectangular container with an optional border (I guess the equivalent of a <div>)?

I see you can have a border with ScrollView although I don’t think you can set properties for the border, and I don’t need the scroll capability in many cases.

If there’s nothing out of the box, what recommendations do you have to create one simply? I’m thinking something where ideally you can specify:

  • whether there is a border
  • border width
  • border colour

To create one, you could use a UserView and draw each border as a coloured rectangle. Any view can be a container for other views.
Best,
Paul

I’m using Views inside layouts a lot, where I might not know (or want to know) the dimensions of the View when I create it. But if I use a UserView, I guess I’d need to know the dimensions?

I was thinking of using a StackedLayout with two views, the view behind being the border. I think the inner view may also need to be nested inside layouts in order to get the dynamic sizing relative to the parent.

Oh, and thank you! Sorry, that was a little rude of me.

This is the closest I can get to the sort of thing I have in mind. The box on the right has a border and everything resizes nicely when you resize the window:

(
~window = Window("What am I doing?").front;
~window.layout = HLayout(
	View().background_(Color.blue).minSize_(200@200).maxWidth_(200),
	StackLayout(
		View().background_(Color(0,0,0,0)).minSize_(200@200),
		View().background_(Color.yellow).minSize_(200@200).layout_(
			HLayout(View().background_(Color.green))
		)
	).mode_(1)
);
)

However, the documentation for StackLayout says this:

Unlike other layouts, StackLayout can not contain another layout.

Clearly in my example the StackLayout contains an HLayout, and the thing still works. Perhaps this is okay because the HLayout is not a direct child of the StackLayout?

I’ll keep working and if I can class-ify it to my satisfaction I’ll post it.

Your suggestion is probably better; I didn’t realise that drawFunc is re-evaluated automatically on resize:

(
~window = Window("What am I doing?").front;
~window.layout = HLayout(
	View().background_(Color.blue).minSize_(200@200).maxWidth_(200),
	UserView().background_(Color.yellow).minSize_(200@200).drawFunc_({
		|me|
		Pen.moveTo(5@5).lineTo((me.bounds.width-5)@5).lineTo((me.bounds.width-5)@(me.bounds.height-5)).lineTo(5@(me.bounds.height-5)).lineTo(5@5).stroke;
	});
);
)

It’s not quite right; just needs a bit more work.

Is this closer to what you’re looking for? UserView behaves more or less like View so it can also hold a layout (or nested layouts, etc.)

(
var win = Window().front;
var container = UserView();
container.drawFunc_({ |v|
    var w = v.bounds.width;
    var h = v.bounds.height;
    var borderWidth = 4;

    Pen.strokeColor_(Color.black);
    Pen.width_(borderWidth);
    Pen.strokeRect(Rect(0,0,w,h));
});

container.layout_(
    HLayout(
        Slider(),
        Slider2D(),
      VLayout(
          Button(),
          Button(),
          Button(),
      ),
    )
);

win.layout_(
    HLayout(
        container
    )
);
)

Ha yes, well done, that looks awesome! strokeRect a good shout too.

1 Like

I often use Pen.addRoundedRect() to get borders with rounded corners; it doesn’t take have to take much to spice up our GUIs! :slight_smile:

And actually, to be precise, I would edit the above with the following: .insetBy so that borderWidth actually shows the whole border, and .joinStyle to clean up the corners.

    Pen.strokeColor_(Color.black);
    Pen.joinStyle_(1);
    Pen.width_(borderWidth);
    Pen.strokeRect(Rect(0,0,w,h).insetBy(borderWidth/2));

Okay, so this is reasonable as a class implementation:

BorderView : UserView {
	var <>borderColor;
	var <>borderRadius = 0;
	var <>borderWidth = 1;

	init {
		|parent,bounds|
		this.drawFunc_({ |v|
			var w = v.bounds.width;
			var h = v.bounds.height;

			if (borderColor.notNil,{
				Pen.strokeColor_(borderColor);
			});

			Pen.width_(borderWidth).joinStyle_(1);
			Pen.addRoundedRect(Rect(0,0,w,h).insetBy(borderWidth/2),borderRadius,borderRadius);
			Pen.stroke;
		});
	}

	*new {
		|parent,bounds|
		^super.new.init(parent,bounds);
	}
}

An example usage:

(
~window = Window("What am I doing?").front;
~window.layout = HLayout(
	View().background_(Color.blue).minSize_(200@200).maxWidth_(200),
	BorderView().background_(Color.yellow).minSize_(200@200).borderColor_(Color.cyan).borderRadius_(10).borderWidth_(2);
);
)

A couple of issues I don’t care enough to look at right now:

  • Since the class extends UserView and simply sets its drawFunc, if the user defines drawFunc for themself they’ll lose the border
  • The background colour can extend past the rounded corners.

Thanks all for your help, this works nicely for me.

Yup, works great as a class! To your questions:

  • a hacky workaround might be declaring the border function as this.prDrawBorder or something, and then overloading the .drawFunc method so it looks something like:
drawFunc_ {  |userDrawFunc|
    userDrawFunc.value(this);
    this.prDrawBorder(this)
}

…but I would actually consider using object composition (“has” a UserView with a border) instead of subclassing (“is” a UserView with a border), as inheritance can sometimes get a bit messy. Consider taking at look at the helpfile for SCViewHolder for one approach.

  • I usually set UserView().background_(Color.clear) and then use Pen.fillColor to set the background color of the Rect, should fix the color bleed at the corners

Oh nice, yes I see. And presumably the issue with the background colour could be solved by extending background_ in such a way that it uses the supplied colour to call fillRect on our drawn rectangle, and sets the View’s actual background to transparent.

Haven’t tried it, but it sounded good in my head.

1 Like

Sorry, hit enter too early and edited my post now!

Thanks, yes, I considered the inheritance vs composition question, but with composition if you wanted something that behaved exactly like a View you’d have to proxy all its members, leading swiftly to a loss of the will to live.

I’ll take a look at SCViewHolder.

But this actually enforces my point about Class inheritance, once you start overloading methods, it’s hard to stop! So instead of overloading .background for BorderView, you could just call BorderView().view.background to change the background of the UserView. This way, BorderView() might not behave exactly like view, but BorderView().view is a View/UserView, so you get everything for free by defining one method:

view {
   ^userViewWithABorder
}

And the good thing with SCViewHolder is that it forwards unknown messages to the view, so if BorderView would be an SCViewHolder subclass, you could just write BorderView().background.

1 Like

If relevant: this is a behavior I’ve implemented within (almost) every custom class I propose within my GUI extension Quark Graphical-Module.

See GMUserView, which has a built-in ‘3 borders system’, which you can subclass, or drawFunc, if needed.

Otherwise, I’d advocate simply using this trick: using a layout to emulate a border:

(
var borderSize = 5;
var borderColor = Color.red;
var border = UserView();


var myView = UserView()
.background_(Color.blue);

border
.background_(borderColor)
.layout_(
	VLayout()
	.margins_(borderSize)
	.add(myView)
).front;

x = border;
)

x.background_(Color.yellow)
x.layout.margins_(0)