Giter Club home page Giter Club logo

Comments (189)

ruckustboom avatar ruckustboom commented on May 12, 2024 1

I'll look into it more after we finish all the new builders we have in queue.

I agree that CSS files are a much better option for most applications. This would probably only really be used for some quick styling in the new SingleViewApp class. I'm just not sure if that is a use case that would merit such a feature.

from tornadofx.

edvin avatar edvin commented on May 12, 2024 1

Nah, hang on - I don't really need anything other than a string to play with this for now :)

from tornadofx.

edvin avatar edvin commented on May 12, 2024 1

OK, so I added a very minimal Stylesheet class that has the render method. We can change this as per your example above, I just did the minimal amount of work to test this now :)

abstract class Stylesheet {
    abstract fun render(): String
}

Create a subclass of stylesheet and return something from render():

class MyTestStylesheet : Stylesheet() {
    // No builder support, faking it for now :)
    override fun render() = ".root { -fx-background: blue }"
}

Now you can import this stylesheet and even make it reload every time your app gains focus:

class CSSTest : SingleViewApp("CSS Test") {
    override val root = HBox(Label("Hello CSS"))

    override fun start(stage: Stage) {
        importStylesheet(MyTestStylesheet::class)
        stage.reloadStylesheetsOnFocus()
        super.start(stage)
    }
}

Now try changing the background to red, recompile and switch back to your app.

Will this do? :)

from tornadofx.

edvin avatar edvin commented on May 12, 2024 1

Fantastic work!! I have some ideas, will post when I get to a computer!

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024 1

Arbitrarily nested selectors now work

from tornadofx.

thomasnield avatar thomasnield commented on May 12, 2024 1

I haven't done CSS too much with JavaFX, but I really like where this is going. I am biased towards writing all things in code so I'll probably be a huge user of this.

from tornadofx.

edvin avatar edvin commented on May 12, 2024 1

The current syntax is good IMHO :)

from tornadofx.

edvin avatar edvin commented on May 12, 2024 1

I have an idea about how to add type safety. First, change the map function to this:

inline fun <reified V> PropertyChunk.map(key: String): ReadWriteProperty<PropertyChunk, V> {
    return object : ReadWriteProperty<PropertyChunk, V> {
        override fun getValue(thisRef: PropertyChunk, property: KProperty<*>) = properties[key] as V

        override fun setValue(thisRef: PropertyChunk, property: KProperty<*>, value: V) {
            properties[key] = value as Any
        }
    }
}

Now the call site gets prettier, and it supports types:

var fontSize: Any by map("-fx-font-size")

If you want to specify type, do:

var backgroundColor: Color by map("-fx-background-color")

Now, setting a Color from a string gets a bit more verbose, so introduce a c() function in PropertyChunk that creates a color from a string, like Kara does.

The next issue that arises from this change is that the buildString function will now do color.toString() which I suspect will not give us the result we want. To fix this, simply check the type of $value in the buildString method, and construct the right kind of string based on the type. This should be a small subset of types to check for.

Btw, I think we should rename the map function to something more specific to avoid confusion/collision. It could also be defined in the PropertyChunk class to reduce its exposure. Maybe cssprop or something

What do you think?

from tornadofx.

edvin avatar edvin commented on May 12, 2024 1

Cool! I just added a feature to SingleViewApp that lets you inline a stylesheet like this:

class CSSApp: SingleViewApp("CSS Test") {
    override val root = HBox()

    init {
        css {
            s(".box") { 
                backgroundColor = Color.BLUE
            }
        }

        with (root) {
            addClass("box")
        }
    }
}

I will try to integrate this into the feature/css-dsl branch to avoid a merge conflict later.

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024 1

Yes, they should. They were originally, but I pulled them out when I was running some tests and forgot to put them back in. I'll fix that.

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

For a node it wouldn't be particularly useful as, in the node's context, all the properties are already accessible.

But it could be nice to have some sort of CSS DSL for the view. Like Kara Framework or something. Just a thought.

from tornadofx.

edvin avatar edvin commented on May 12, 2024

That's an interesting idea. It would certainly be possible, even to include every possible property, since it's just CSS 2.1 after all. We could also create some beautiful builders for gradients etc :)

I'm unsure of wether users would prefer this over editing a normal CSS file though. After all, IDE's have pretty spectacular CSS support these days.

from tornadofx.

edvin avatar edvin commented on May 12, 2024

If you want to play with this, maybe Kara is a good place to look for inspiration?

from tornadofx.

edvin avatar edvin commented on May 12, 2024

I agree it would be very cool :) Other, more important things first though :)

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

You should mark this as an enhancement, and we can get to it later.

Here is the kara CSS DSL for future reference.

from tornadofx.

ronsmits avatar ronsmits commented on May 12, 2024

Hmmz then why not add SASS?

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

Do you mean make the DSL look more like SASS or create a SASS parser?

from tornadofx.

ronsmits avatar ronsmits commented on May 12, 2024

Make it look like SASS

from tornadofx.

edvin avatar edvin commented on May 12, 2024

@ronsmits I remember something about that from the Kara Framework. Since the DSL is written in Kotlin, we can do even more powerful things than SCSS out of the box, so there is no need to mimic a potentially inferior syntax :) That might sound cocky, but just think of the possibilities when you can call actual Kotlin functions within your stylesheet. Now that's power :)

That might actually be the one solid argument for doing this - a lot more expressive syntax for JavaFX CSS. This might be a killer feature if done right!

from tornadofx.

ronsmits avatar ronsmits commented on May 12, 2024

I agree, go for it

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

Does anyone have any experience with the Kara framework? I do not, but it would be nice to hear if there is anything about the Kara CSS DSL that should be changed/added/removed.

For example, the overridden render function seems strange to me. Personally I think it would suit TornadoFX better to have the function in the constructor. So instead of (their example):

class DefaultStyles() : Stylesheet() {
    override fun render() {
        s("body") {
            backgroundColor = c("#f0f0f0")
        }
        s("#main") {
            width = 85.percent
            backgroundColor = c("#fff")
            margin = box(0.px, auto)
            padding = box(1.em)
            border = "1px solid #ccc"
            borderRadius = 5.px
        }

        s("input[type=text], textarea") {
            padding = box(4.px)
            width = 300.px
        }
        s("textarea") {
            height = 80.px
        }

        s("table.fields") {
            s("td") {
                padding = box(6.px, 3.px)
            }
            s("td.label") {
                textAlign = TextAlign.right
            }
            s("td.label.top") {
                verticalAlign = VerticalAlign.top
            }
        }
    }
}

it could just be

val styles = Stylesheet().apply {
    s("body") {
        backgroundColor = c("#f0f0f0")
    }
    s("#main") {
        width = 85.percent
        backgroundColor = c("#fff")
        margin = box(0.px, auto)
        padding = box(1.em)
        border = "1px solid #ccc"
        borderRadius = 5.px
    }

    s("input[type=text], textarea") {
        padding = box(4.px)
        width = 300.px
    }
    s("textarea") {
        height = 80.px
    }

    s("table.fields") {
        s("td") {
            padding = box(6.px, 3.px)
        }
        s("td.label") {
            textAlign = TextAlign.right
        }
        s("td.label.top") {
            verticalAlign = VerticalAlign.top
        }
    }
}

and in a View have a css method

init {
    css {
        s("body") {
            backgroundColor = c("#f0f0f0")
        }
        s("#main") {
            width = 85.percent
            backgroundColor = c("#fff")
            margin = box(0.px, auto)
            padding = box(1.em)
            border = "1px solid #ccc"
            borderRadius = 5.px
        }

        s("input[type=text], textarea") {
            padding = box(4.px)
            width = 300.px
        }
        s("textarea") {
            height = 80.px
        }

        s("table.fields") {
            s("td") {
                padding = box(6.px, 3.px)
            }
            s("td.label") {
                textAlign = TextAlign.right
            }
            s("td.label.top") {
                verticalAlign = VerticalAlign.top
            }
        }
    }

    // TODO: Other stuff
}

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

I've been messing around trying to get a very small proof of concept, but I've run into a problem. It seems JavaFX does not allow you to add raw CSS as a stylesheet, but requires URLs. There is a way around it, but as far as I can tell, it isn't very flexible (e.g. you can only ever add a single stylesheet, and it has to be a string).

from tornadofx.

edvin avatar edvin commented on May 12, 2024

Ah, I remember that, I've seen a couple of different workarounds. I'll mess with this a little myself and report back. I also have a couple of ideas about what we can do to make this even more valuable, but I need to think about it a little more before I write something down :)

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

Do you want me to send you what I have (just a shell for a CSS DSL), or would you prefer a clean approach?

from tornadofx.

edvin avatar edvin commented on May 12, 2024

I think I found a good way to implement a custom URL handler for our stylesheets, without using any hacks, just plain Java APIs :) I'll need a couple of minutes, will post back soon.

from tornadofx.

edvin avatar edvin commented on May 12, 2024

The handler is very simple, and it is automatically registered for the new and shiny css:// protocol :)

Give it an url to a fully qualified class name that implements the Stylesheet class, and you're good to go:

val css = URL("css://com.mycompany.css.MainStylesheet")

This can be added to a stage manually as well:

scene.stylesheets.add(css.toExternalForm())

The implementation is basically this:

override fun getInputStream(): InputStream {
    val stylesheet = Class.forName(url.host).newInstance() as Stylesheet
    return stylesheet.render().byteInputStream(StandardCharsets.UTF_8)
}

I've commited it now so you can play with it :)

from tornadofx.

edvin avatar edvin commented on May 12, 2024

Here is a small screencast of the concept in action:

https://www.youtube.com/watch?v=hpRxwuwpR_o

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

That's pretty slick :)

I don't think it can be integrated with a DSL very well though, as you wouldn't have a fully qualified class path in that case. Or am I missing something?

from tornadofx.

edvin avatar edvin commented on May 12, 2024

That's no problem at all really :) Can you show me some of your code and I'll make it work :)

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

Nevermind :) I think I figured it out.

from tornadofx.

edvin avatar edvin commented on May 12, 2024

Nice. If you run into problems getting a class out of it, we could always just render the stylesheet to a basic64 encoded string and included it in the url itself. It would be trivial to change the urlhandler to handle that. Let me know, and I'll make the change :) Can you work within the CSS.kt that I added?

Would be cool if we could include preliminary CSS support in the next release!

from tornadofx.

edvin avatar edvin commented on May 12, 2024

When you do:

init {
    css {
        ...
    }
}

in a View... where do you want this stylesheet applied?

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

I've got a very basic prototype working. I'll see if I can flesh it out a bit more by the end of the day.

Current issues:

  • All selectors and properties are entered as strings (s(".test-class") { prop("font-size", 18.px) })
    • I would like to have something more like s(".test-class") { fontSize = 18.px }
  • Nested selectors don't work past two levels (this should be an easy fix)
    • s("a") { s("b") { s("c") {} } } returns a {} a b {} b c {} instead of a {} a b {} a b c {}
  • Mixins not working

As far as where to apply the stylesheet, I'm not sure. For now I'm just doing it in the overridden start(Stage) method.

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

I've now got the s(".test-class") { fontSize = 18.px } to work properly (thanks to this answer.) I just have to go add all the possible properties.

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

Demo so far:

class CssTest : SingleViewApp("CSS Test") {
    override val root = VBox()

    init {
        with(root) {
            prefWidth = 400.0
            prefHeight = 400.0

            label("test") {
                styleClass += "fred"
            }
            hbox {
                styleClass += "box"
                padding = Insets(5.0)
                label("test 2")
            }
            piechart("Imported Fruits") {
                data("Grapefruit", 12.0)
                data("Oranges", 25.0)
                data("Plums", 10.0)
                data("Pears", 22.0)
                data("Apples", 30.0)
            }
        }
    }

    override fun start(stage: Stage) {
        class MyTestStylesheet : Stylesheet() {
            init {
                s(".label") {
                    fontSize = 18.px
                    textFill = "orange"
                }
                s(".label.fred") {
                    textFill = "blue"
                }
                s(".box") {
                    backgroundColor = "green"

                    s(".label") {
                        backgroundColor = "red"
                        textFill = "white"
                    }
                }
            }
        }
        importStylesheet(MyTestStylesheet::class)

        println(MyTestStylesheet())
        super.start(stage)
    }
}

css-tests

Generated CSS:

.label {
    -fx-font-size: 18px;
    -fx-text-fill: orange;
}

.label.fred {
    -fx-text-fill: blue;
}

.box {
    -fx-background-color: green;
}

.box .label {
    -fx-background-color: red;
    -fx-text-fill: white;
}

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

Any ideas are appreciated.

Demo with Mixin (dumb but gets the point across):

class CssTest : SingleViewApp("CSS Test") {
    override val root = VBox()

    init {
        with(root) {
            prefWidth = 400.0
            prefHeight = 400.0

            label("test") {
                styleClass += "fred"
            }
            hbox {
                styleClass += "box"
                label("test 2")
            }
            piechart("Imported Fruits") {
                data("Grapefruit", 12.0)
                data("Oranges", 25.0)
                data("Plums", 10.0)
                data("Pears", 22.0)
                data("Apples", 30.0)
            }
        }
    }

    override fun start(stage: Stage) {
        class MyTestStylesheet : Stylesheet() {
            init {
                val pad = mixin {
                    prop("padding", 5.px)

                    s(".label") {
                        backgroundColor = "red"
                        textFill = "white"
                    }
                }
                s(".label") {
                    +pad
                    fontSize = 18.px
                    textFill = "orange"
                }
                s(".label.fred") {
                    textFill = "blue"
                }
                s(".box") {
                    +pad
                    backgroundColor = "green"
                }
            }
        }
        importStylesheet(MyTestStylesheet::class)

        println(MyTestStylesheet())
        super.start(stage)
    }
}

css-tests-mixin

.label {
    -fx-padding: 5px;
    -fx-font-size: 18px;
    -fx-text-fill: orange;
}

.label .label {
    -fx-background-color: red;
    -fx-text-fill: white;
}

.label.fred {
    -fx-text-fill: blue;
}

.box {
    -fx-padding: 5px;
    -fx-background-color: green;
}

.box .label {
    -fx-background-color: red;
    -fx-text-fill: white;
}

from tornadofx.

edvin avatar edvin commented on May 12, 2024

The mixin feature is fantastic. OK, here goes my idea. Look at the Java FX 8 CSS reference for the Labeled class:

https://docs.oracle.com/javase/8/javafx/api/javafx/scene/doc-files/cssref.html#labeled

It basically tells us every possible property that can be specified for .label. It would be fantastic if it was possible to "tell" a style declaration that it will only be applied to a Labeled, so that it would only accept the style properties defined for a labeled:

s("#heading", Labeled::class) {
    underline = true // This is OK
    orientation = Orientation.HORIZONTAL // Compile error, orientation is not defined for Labeled
}

This might be a pipe dream, but I thought it was worth mentioning it. When you have defined all properties in the Labeled, defining the others are much simpler. For example, ButtonBase would extend Labeled and only add the armed pseudo class.

When you don't specify a class, the default would have to be a class that includes all the subclasses, so that these two declarations would mean the same:

s("#heading") {
    // Anything goes
}

s("#heading", AllClasses::class) {
   // Anything goes here too :)
}

Again, I suspect this is not doable, and might not even be worth it, but I wanted to throw it out there if you want to play with it or at least thing about the implications that would have on the model :)

from tornadofx.

edvin avatar edvin commented on May 12, 2024

The color properties could also accept javafx.scene.paint.Color instances, to increase type safety, don't you think? All these three could be possible:

s("#heading") {
    backgroundColor = Color.BLACK
    backgroundColor = Color(0.0, 0.0, 0.0)
    backgroundColor = "black"
    backgroundColor = "#000"
}

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

One potential downside with the limiting (assuming it's possible) could be scope confusion. For example, if the s("#heading", Labeled::class) { ... } was inside something that did have an orientation property, your above example would compile fine, but apply to the outside selection instead of the #heading.

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

I'll have to look into supporting colors with better type safety. Currently, properties are stored in a MutableMap<String, Any>. I'm not sure how to add good type safety with that.

An example property: var fontSize: Any by map(properties, "-fx-font-size")

from tornadofx.

edvin avatar edvin commented on May 12, 2024

I think the scoping issue is possible to avoid, I think Kara solved that with the way they created the class hierarchy (I might be mistaken, but I think I read that somewhere). The reason we have that problem in TornadoFX is that we use extension functions. This can be avoided for a "evergreen" DSL like this one.

Could I have a look at what you have so far? Easier to give informed advice that way :)

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

Sure thing. Here is the link to the working branch. I try to keep it up to date as I go.

It's pretty bare bones (just testing out ideas) and the names could be better :)

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

I'm not married to what I've done so far, so if throwing it out and starting from scratch would give a better result, I'm all for it.

from tornadofx.

edvin avatar edvin commented on May 12, 2024

OK, I'll play with a little and see how it feels and report back :)

from tornadofx.

edvin avatar edvin commented on May 12, 2024

I'm afraid I'm just too tired to do any more today :) Will try to look more at it tomorrow!

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

Is the current selector syntax good:

s(".bad") {
    +mixin
    textFill = "red"
    s(".label") {
        backgroundColor = "black"
    }
}

or would an infix be better:

".bad" style {
    +mixin
    textFill = "red"
    ".label" style {
        backgroundColor = "black"
    }
}

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

That sounds great. I'll try it out and see how it goes.

As far as renaming goes, the StyleChunk and PropertyChunk names are just placeholders I used because I didn't feel like thinking of better names at the time. Any suggestions for better names.

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

So far is seems to work fantastically!

Just a heads up that I'm rendering Colors as rgba(...) instead of #... as JavaFX Colors have opacity. I'll have to look into what I can do with other types (including enums).

from tornadofx.

edvin avatar edvin commented on May 12, 2024

Cool :) This is turning out really great :)

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

Obligatory demo:

class CssTest : SingleViewApp("CSS Test") {
    override val root = VBox()

    init {
        with(root) {
            prefWidth = 400.0
            prefHeight = 400.0

            hbox {
                styleClass += "box"

                label("Alice")
                label("Bob")
            }
        }
    }

    override fun start(stage: Stage) {
        class MyTestStylesheet : Stylesheet() {
            init {
                val pad = mixin {
                    padding = 5.px
                }

                s(".box") {
                    +pad
                    backgroundColor = Color.BLUE
                    spacing = 5.px

                    s(".label") {
                        +pad
                        fontSize = 18
                        textFill = Color.WHITE
                        backgroundColor = Color.GREEN
                    }
                }
            }
        }
        importStylesheet(MyTestStylesheet::class)

        println(MyTestStylesheet())
        super.start(stage)
    }
}

css-tests-type-safety

.box {
    -fx-padding: 5px;
    -fx-background-color: rgba(0, 0, 255, 1.0);
    -fx-spacing: 5px;
}
.box .label {
    -fx-padding: 5px;
    -fx-font-size: 18;
    -fx-text-fill: rgba(255, 255, 255, 1.0);
    -fx-background-color: rgba(0, 128, 0, 1.0);
}

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

If there is an error parsing the color string (with the c(String) function), what should I default to?

Options:

  • Allow nulls in the property map and either ignore them or default them to "inherit"
  • Choose default values for any custom constructor functions

from tornadofx.

edvin avatar edvin commented on May 12, 2024

If you enter an invalid property value in a javafx css stylesheet, the directive is ignored and an error is logged. Therefore I think we should use the same approach.

from tornadofx.

edvin avatar edvin commented on May 12, 2024

Oh, and super nice work, I really love the way this is shaping up!

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

Sounds good

from tornadofx.

edvin avatar edvin commented on May 12, 2024

We should probably also have special support for padding/insets, like the box() function in Kara.

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

I've made dimensions type safe (like in Kara), but it has one downside. All dimensions require a unit. You can no longer use fontSize = 14, you would have to do something like fontSize = 14.pt. Is that acceptable, or should I allow Any for linear dimensions?

(Current accepted units: px, mm, cm, inches, pt, pc, em, ex, percent)

from tornadofx.

edvin avatar edvin commented on May 12, 2024

Perfect! That is totally acceptable :)

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

I've now got most of the properties added (with enums for the ones with limited choices). The only ones left to do are ones that I need to spend some more time figuring out how to do. Feel free to check out what I have so far. In the meantime, I'm going to bed :).

Also, anything with a fill can accept any Paint, not just Colors.

There are also linear dimensions (see above), box dimensions (basically just 4 linear dimensions (this is what the box() function uses (for padding, margin, border radius, etc))), angular dimensions (deg, rad, grad, turn), and temporal dimensions (s, ms).

from tornadofx.

edvin avatar edvin commented on May 12, 2024

Fantastic work! I really like the approach you have taken here. It seems to work really well. Please let me know if there are certain areas you need to discuss or need feedback on. I will start playing with this to see how it feels in the mean time.

from tornadofx.

edvin avatar edvin commented on May 12, 2024

By the way @t-boom - would you like commit access to the repo? You might as well integrate this stuff directly instead of going via a PR. We could start integrating the stuff and work together on it as well. I've put some stuff in CSS.kt so you would have to merge with that.

Let me know if it's OK with you, and I'll add you as a contributor on the project.

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

Sure, that would probably make it easier for both of us.

I've merged in your changes (and pushed it up to my repo for now)

from tornadofx.

edvin avatar edvin commented on May 12, 2024

Hereby granted :) I'm thinking we could do another release as soon as this DSL is done - it's a major feature! Feel free to delegate work to me after you have checked in what you have so far :)

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

Alright. I've pushed what I've got so far to the feature/css-dsl branch. I'll let you know what I need help with.

from tornadofx.

edvin avatar edvin commented on May 12, 2024

The change described above is now in the feature/css-dsl branch :)

I have a question about the use of extension functions. Take a look at this one for example:

fun <T> SelectionBlock.box(all: T) = CssBox(all, all, all, all)

Shouldn't this just be a member function of SelectionBlock? Same goes for some of the other extension functions - it seems like they could just as well be member functions. What do you think?

from tornadofx.

edvin avatar edvin commented on May 12, 2024

OK! I also just made Stylesheet an open class like it was in your original design. I mistakenly changed it to abstract when I was playing around. It is nice to be able to instantiate a Stylesheet and then operate on it (see SingleViewApp for an example). Sorry about that :)

from tornadofx.

edvin avatar edvin commented on May 12, 2024

I saw you used com.sun.xml.internal.fastinfoset.util.StringArray in a couple of places. This class will be unavailable in JDK9. Did you use it to be able to check the type in toCSS?

If so, say the word and I'll try to implement a workaround :)

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

That was another Accident :) That should have just been Array. I'll fix that too. Thanks!

from tornadofx.

edvin avatar edvin commented on May 12, 2024

Sweet! Let me know if you have stuff you want to delegate to me :)

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

For properties that accept a URI, should I just accept in a String?

Also, there are a number of properties that accept a set of values plus some user defined thing. The cursor is a good example as it can accept from a predefined list or a uri to an image. What should I do with those?

from tornadofx.

edvin avatar edvin commented on May 12, 2024

String for URI sounds reasonable to me.

I'll think about the other issue for a bit. Wonder if Kara faced/solved similar issues...

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

I haven't looked. I figured I'd get back to it later once I get the more tedious parts out of the way, but if you want to look into it, that would be awesome.

from tornadofx.

edvin avatar edvin commented on May 12, 2024

One possibility:

cursor: EnumValue
cursorUri: String

Haven't thought it through or tested how it feels, just throwing it out there :)

from tornadofx.

edvin avatar edvin commented on May 12, 2024

OK, I will have a look tonight and report back!

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

That would work for the cursor, but there are others that are more complicated. -fx-background-position in Region is pretty bad.

from tornadofx.

edvin avatar edvin commented on May 12, 2024

The type should probably be javafx.scene.layout.BackgroundPosition and then we could add convenience functions later. What do you think?

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

That's a good idea. Many of the others can probably be solved similarly.

from tornadofx.

edvin avatar edvin commented on May 12, 2024

Yes, it seems that way, including Cursor.

cursor = Cursor.CROSSHAIR
cursor = Cursor.cursor("http://coolcursors.com/cursor1.png")

To get the cursor name you would have to do something like:

        val cursorName = if (cursor is ImageCursor) {
            cursor.image.javaClass.getDeclaredField("url").let {
                it.isAccessible = true
                it.get(cursor.image)
            }
        } else {
            cursor.toString()
        }

A bit ugly, but it works :)

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

We'll go with that.

Three more questions:

  1. Should I have all the enums follow standard naming conventions (ALL_CAPS)?
  2. Do you want to check to see which enums I've defined that can be replaced by existing JavaFX classes?
  3. Will is Enum work for enums defined with enum class ...?

from tornadofx.

edvin avatar edvin commented on May 12, 2024
  1. Yes, but mainly since the JavaFX enums are ALL_CAPS
  2. OK, I'll go over them
  3. Yes, it should, if not do java.lang.Enum::class.java.isAssignableFrom(x.javaClass)

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

All properties have now been added, but not all render properly (I still need to finish font, effect and borderStyle). There are a few that still need validated better, and several that can probably be replaced with builtin classes / enums.

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

As far as the skin property goes, the application will crash if you provide a skin name that doesn't exist. Should I require a skin class? I'm not sure how the skin property works.

from tornadofx.

edvin avatar edvin commented on May 12, 2024

Great! Looking for enums that can be avoided now. Yeah, we can require a skin class, I can't see how that should be a problem. Either it's on the classpath or it's not. In a pinch, if all the stylesheet author has is a String, a class could be generated with Class.forName("skinClass").kotlin.

from tornadofx.

edvin avatar edvin commented on May 12, 2024

Btw, just found a bug in SingleViewApp that i fixed in the master branch. Basically, both the JavaFX Application Launcher and the TornadoFX di framework created an instance of the App class. I'll apply the fix in this branch as well, just in case it bites us while working on this :)

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

Is that why the CSS is always created twice?

from tornadofx.

edvin avatar edvin commented on May 12, 2024

Yes :) Can you verify that it is only created once if you pull my latest change?

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

Just tried it. It seems to working great.

from tornadofx.

edvin avatar edvin commented on May 12, 2024

Phew :)

from tornadofx.

edvin avatar edvin commented on May 12, 2024

There are several enums that can probably be replaced. Two examples:

FXContentDisplay -> ContentDisplay
FXTextOverrun -> OverrunStyle

It seems the enums can be used by converting the name to lowercase and switching underscore with dash.

If I understand this correctly, all we would have to do is check if the value is an enum in toCss and do tolowercase and replace _ with -.

Should I go ahead and try that for a couple of the enums?

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

That was my thought too (thus my question above about checking is Enum). Go for it.

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

Another (ugly) progress demo:

class CssTest : SingleViewApp("CSS Test") {
    override val root = VBox()

    init {
        css {
            val pad = mixin {
                padding = box(1.em)
                borderColor = box(LinearGradient(0.0, 0.0, 10.0, 10.0, false, CycleMethod.REFLECT, Stop(0.0, Color.RED), Stop(1.0, c(0.0, 1.0, 0.0))))
                borderWidth = box(5.px)
            }

            s(".box") {
                +pad
                backgroundColor = RadialGradient(90.0, 0.5, 0.5, 0.5, 0.25, true, CycleMethod.REFLECT, Stop(0.0, Color.WHITE), Stop(1.0, Color.BLACK))
                spacing = 5.px

                s(".label") {
                    +pad
                    fontSize = 14.pt
                    textFill = c("white")
                    backgroundColor = c("blue", 0.75)
                    rotate = .95.turn
                    translateX = .5.inches
                    minHeight = 6.em
                    scaleX = 2
                    scaleY = .75
                    backgroundRadius = box(25.px)
                    borderRadius = box(25.px)
                }
            }
        }

        with(root) {
            prefWidth = 400.0
            prefHeight = 400.0

            hbox {
                styleClass += "box"

                label("Alice")
                label("Bob")
            }
        }
    }
}

css-tests-ugly

.box {
    -fx-padding: 1em 1em 1em 1em;
    -fx-border-color: linear-gradient(from 0.0px 0.0px to 10.0px 10.0px, reflect, rgba(255, 0, 0, 1) 0.0%, rgba(0, 255, 0, 1) 100.0%) linear-gradient(from 0.0px 0.0px to 10.0px 10.0px, reflect, rgba(255, 0, 0, 1) 0.0%, rgba(0, 255, 0, 1) 100.0%) linear-gradient(from 0.0px 0.0px to 10.0px 10.0px, reflect, rgba(255, 0, 0, 1) 0.0%, rgba(0, 255, 0, 1) 100.0%) linear-gradient(from 0.0px 0.0px to 10.0px 10.0px, reflect, rgba(255, 0, 0, 1) 0.0%, rgba(0, 255, 0, 1) 100.0%);
    -fx-border-width: 5px 5px 5px 5px;
    -fx-background-color: radial-gradient(focus-angle 90.0deg, focus-distance 50.0% , center 50.0% 50.0%, radius 25.0%, reflect, rgba(255, 255, 255, 1) 0.0%, rgba(0, 0, 0, 1) 100.0%);
    -fx-spacing: 5px;
}
.box .label {
    -fx-padding: 1em 1em 1em 1em;
    -fx-border-color: linear-gradient(from 0.0px 0.0px to 10.0px 10.0px, reflect, rgba(255, 0, 0, 1) 0.0%, rgba(0, 255, 0, 1) 100.0%) linear-gradient(from 0.0px 0.0px to 10.0px 10.0px, reflect, rgba(255, 0, 0, 1) 0.0%, rgba(0, 255, 0, 1) 100.0%) linear-gradient(from 0.0px 0.0px to 10.0px 10.0px, reflect, rgba(255, 0, 0, 1) 0.0%, rgba(0, 255, 0, 1) 100.0%) linear-gradient(from 0.0px 0.0px to 10.0px 10.0px, reflect, rgba(255, 0, 0, 1) 0.0%, rgba(0, 255, 0, 1) 100.0%);
    -fx-border-width: 5px 5px 5px 5px;
    -fx-font-size: 14pt;
    -fx-text-fill: rgba(255, 255, 255, 1);
    -fx-background-color: rgba(0, 0, 255, 0.74902);
    -fx-rotate: 0.95turn;
    -fx-translate-x: 0.5in;
    -fx-min-height: 6em;
    -fx-scale-x: 2;
    -fx-scale-y: 0.75;
    -fx-background-radius: 25px 25px 25px 25px;
    -fx-border-radius: 25px 25px 25px 25px;
}

from tornadofx.

edvin avatar edvin commented on May 12, 2024

Fantastic :)

I'm running into some problems with checking if value: T is an enum in toCss. Either I have to make the function inline so that T can be reified, or I have to change the bounds of T to T : Any.

The first makes it impossible to recurse into toCss (needed for Array/Pair etc), and the other makes it necessary to perform some casts to Any when calling toCss from a class with type parameters, like CssBox.

Don't love any of those approaches. Will try some more.

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

I'm not sure if I understand. Why won't

is Enum<*> -> return value.toString().toLowerCase().replace("_", "-")

work?

from tornadofx.

edvin avatar edvin commented on May 12, 2024

Haha, that works, I think I'm too tired, midnight here already :) I'll revert that crazyness and commit the removed enums etc.

from tornadofx.

edvin avatar edvin commented on May 12, 2024

What about this one?

null -> return ""  // This should only happen in a container TODO: Is there a better way to handle this?

Do you need this check at all? All passed in types are non-null.

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

Yes, because anything inside a box, pair, array, etc. that is null somehow still makes it into that function without causing an error.

borderColor = box(Color.Green, null)

would create

-fx-border-color: rgba(0, 255, 0, 1) null rgba(0, 255, 0, 1) null;

from tornadofx.

edvin avatar edvin commented on May 12, 2024

Ah :) Just committed, I'm going to bed before I do any more harm :)

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

Anything that accepts color has to accept Paint?. It was the only way to handle trying to create invalid colors (JavaFX throws an error, so I return a null and have the property setter ignore it; unfortunately the property setter can't look inside containers, so I had to put something there, and a blank string seems to do the least damage in CSS).

from tornadofx.

edvin avatar edvin commented on May 12, 2024

Sorry for being dense here, but I don't quite understand (even after a little sleep, hehe :)

var backgroundColor: Paint?

Why does it help to declare this as nullable? I don't understand the part about creating invalid colors :) Can you give me an example?

from tornadofx.

ruckustboom avatar ruckustboom commented on May 12, 2024

When you use JavaFX's methods to create a color, it throws an exception if you give bad values, so I catch the exceptions and return null. If you can think of a good way to avoid nulls, that would be awesome, as we could then make cssprop type <reified T : Any>

from tornadofx.

edvin avatar edvin commented on May 12, 2024

Ah, OK, I will investigate and get back to you :)

from tornadofx.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.