I need a white box (I'm using a Panel because I'm so close, but it could be anything) with all four corners rounded, and the top portion of the box needs to have a gradient that starts out one color (let's say Grey) and ends up as the background color (white). The gradient does not go down the entire box.
The top two corners must remain rounded, so the gradient must fill those rounded regions too.
What I have so far is an mx:Panel with the following styles:
Panel {
borderColor: #ffffff;
borderAlpha: 1;
borderThickness: 13;
borderThicknessLeft: 0;
borderThicknessTop: 0;
borderThicknessBottom: 11;
borderThicknessRight: 0;
roundedBottomCorners: true;
cornerRadius: 16;
headerHeight: 50;
backgroundAlpha: 1;
highlightAlphas: 0.29, 0;
headerColors: #999999, #ffffff;
backgroundColor: #ffffff;
dropShadowEnabled: false;
}
As you can see, there is one tiny single-pixel line that cuts across the header. If I can just get rid of that single-pixel line, that would be perfect! I have tried changing the borderStyle property to "solid" and still cannot get the right combination of styles to get rid of that line.
I have also tried using another container with a background image for the gradient, but the rounded corners become an issue.
Any help would be greatly appreciated!
The culprit for the line under the title bar is actually the default titleBackgroundSkin associated with the Panel component (mx.skins.halo.TitleBackground). If you look at the source of the updateDisplayList method you see a section where it draws the background that includes the following lines:
g.lineStyle(0, colorDark, borderAlpha);
g.moveTo(0, h);
g.lineTo(w, h);
g.lineStyle(0, 0, 0);
If you create your own implementation that does everything the same with the exception of these lines, you should get what you need. At least I did in my limited test case with a gradient header.
The following css gets rid of the line, but prevents you from using a gradient header.
.richTextEditor
{
titleBackgroundSkin: ClassReference("mx.skins.ProgrammaticSkin"); /** Gets rid of the line that was there **/
}
I didn't find a solution to the specific Panel issue above, but I did find an overall solution that works for any container: set a background image using bitmapFill and round the corners.
This is what worked for me (using a programmatic skin):
[styles.css]
HBox {
borderSkin: ClassReference("assets.programmatic.GradientHeaderSkin");
}
[GradientHeaderSkin.as]
package assets.programmatic
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import mx.skins.ProgrammaticSkin;
public class GradientHeaderSkin extends ProgrammaticSkin
{
[Embed(source="../imgs/panelHeaderGradient.png")]
private var _backgroundImageClass:Class;
private var _backgroundBitmapData:BitmapData;
private var _backgroundImage:Bitmap;
private var _backgroundColor:uint;
public function GradientHeaderSkin()
{
super();
_backgroundImage = new _backgroundImageClass();
_backgroundBitmapData = new BitmapData(_backgroundImage.width, _backgroundImage.height);
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
_backgroundBitmapData.draw(_backgroundImage);
graphics.clear();
graphics.beginBitmapFill(_backgroundBitmapData, null, false, false);
// specify corner radius here
this.graphics.drawRoundRectComplex(0, 0, this.width, this.height, 20, 20, 20, 20);
}
}
}
Here is also an example which loads an external image:
http://books.google.com/books?id=7fbhB_GlQEAC&pg=PA106&lpg=PA106&dq=flex+filling+rounded+corners+with+graphic&source=bl&ots=HU_jainH4F&sig=F793bjL0a4nU5wx5wq608h_ZPr0&hl=en&ei=GQd3Spi-OYiYMcLDyLEM&sa=X&oi=book_result&ct=result&resnum=1#v=onepage&q=&f=false
Related
I have a toggle button in my program that starts/stops a script. I would like for this button to be green and say "START" when the button is not selected, and red and say "STOP" when it is selected. More importantly, I would like the unselected hover color to be a slightly darker version of the original green, and the selected hover color to be a slightly darker version of the red color. My current CSS for this button looks like this:
#startStopButton {
-fx-border-color:#d4d4d4;
-fx-background-color:#85eca5;
-fx-background-image: url("startButton.png");
-fx-background-size: 50px;
-fx-background-repeat: no-repeat;
-fx-background-position: 80% 50%;
-fx-alignment: CENTER_LEFT;
-fx-effect: dropshadow(three-pass-box, #e7e7e7, 15, 0, 0, 0);
}
#startStopButton:hover {
-fx-background-color:#80dc9c;
}
#startStopButton:selected{
-fx-background-color: #ff6060;
-fx-text:"STOP";
}
#startStopButton:selected:focused{
-fx-background-color: #ff6060;
-fx-text:"STOP";
}
Currently, this will work fine, except for when the button turns red. In this case, there is no hover effect. Within my FXML controller, there is a function that is activated every time this button is clicked:
private void startStopClick()
{
if(startStopButton.isSelected())
{
startStopButton.setText(" STOP");
// startStopButton.setStyle()
}
else {
startStopButton.setText(" START");
}
}
Is there any way to 1) set the button text within CSS so that I can leave that out of my controller?
2) Get the current toggle button state in CSS, so that I can have multiple hover effects. For example, something like this:
#startStopButton:unselected{
-fx-background-color: #ff6060;
-fx-text:"STOP";
}
If there is no way to do this in CSS, can I set the hover styles in the Java code in the FXML controller?
CSS properties are only available for the look of nodes. With a few exceptions the basic JavaFX nodes don't allow you to specify content via CSS. The text property of buttons is no exception; it cannot be set using CSS.
As for the colors: The rules occuring last override values assigned by rules with the same precedence occuring before them. This means the background color assigned by the rules for #startStopButton:selected and #startStopButton:selected:focused always override the color #startStopButton:hover assigns.
Since in both cases you want a darker color when hovering, the derive function and a lookedup color may work for you.
Example
#Override
public void start(Stage primaryStage) {
ToggleButton btn = new ToggleButton();
btn.getStyleClass().add("start-stop");
btn.textProperty().bind(Bindings.when(btn.selectedProperty()).then(" STOP").otherwise(" START"));
Pane p = new Pane(btn);
Scene scene = new Scene(p);
scene.getStylesheets().add("style.css");
primaryStage.setScene(scene);
primaryStage.show();
}
style.css
.start-stop.toggle-button {
base-color: #85eca5;
-fx-background-color: base-color;
}
.start-stop.toggle-button:selected {
base-color: #ff6060;
}
.start-stop.toggle-button:hover {
-fx-background-color: derive(base-color, -20%);
}
If you cannot use derive since you need to specify different colors for all 4 states you could still rely on looked-up colors to avoid relying on the rule ordering:
.start-stop.toggle-button {
unselected-color: blue;
selected-color: yellow;
-fx-background-color: unselected-color;
}
.start-stop.toggle-button:hover {
unselected-color: red;
selected-color: green;
}
.start-stop.toggle-button:selected {
-fx-background-color: selected-color;
}
I'm trying to emulate the CTRL+F functionality from Chrome that highlights matches on the page in the scrollbar, but for certain fields in a form. Using page offsets and percentages, I have blocks of color which correspond to the relative locations of those fields on the page.
In my prototype, the blocks of color sit to the left of the scrollbar. Ideally, they'd sit UNDERNEATH the scrollbar, and the scrollbar's track would be transparent so that it looks like they're part of the scrollbar track.
Can the default scrollbar be set to allow for overflow content to show underneath it (or allow page content to go over it)? I know this could be accomplished if I just rolled my own scroll, but I'd like to use the default ones provided by the browser if at all possible.
It's clearest if you just look at this Prototype.
CSS:
::-webkit-scrollbar {
width: 14px;
height: 18px;
background-color:transparent;
}
::-webkit-scrollbar-track,
::-webkit-scrollbar-track-piece {
background:none;
}
::-webkit-scrollbar-thumb {
height: 6px;
border: 4px solid rgba(0, 0, 0, 0);
background-clip: padding-box;
-webkit-border-radius: 7px;
background-color: #333
}
::-webkit-scrollbar-button {
width: 0;
height: 0;
display: none;
}
::-webkit-scrollbar-corner {
background-color: transparent;
}
I thought of rendering the matches on the trackbar similarly to what browsers do today before. The idea is simple by using linear-gradient background for the ::-webkit-scrollbar-track. However I did not try implementing this. Right after reading your question, I've tried it and looks like it's not such easy.
You can use the linear-gradient background OK, but if you try rendering more than 1 match (a line), it sometimes can't be rendered (especially when the window's size is changed) and the line is not rendered smoothly. Such as this seems to be OK:
//render 2 lines, one is at 50px and the other is at 100px
background: linear-gradient(transparent 50px, red 50px, red 51px, transparent 51px,
transparent 100px, red 100px, red 101px, transparent 101px);
but it's not stable, as I said when you try resizing the window, at some size, some line won't be rendered (at least I tried on Opera). When the window's height is large, the line even becomes blurred (not sharp) and thicker. I don't really understand this, because the color stops are set fixedly (by px, not by %). This issue is even worse when the number of lines is larger. You have a linear-gradient with many corresponding color stops. That seems to be a neat way to solve the problem. Just because of the undesired issue, we can't use that approach.
The new approach: So I tried using multi-backgrounds feature instead. Each background just renders 1 line, the background-size is the same for all the background is just about 2px height and the background-position should be different. Here is the equivalent code (to the above clean code) using this approach:
background: linear-gradient(red, red), linear-gradient(red, red);
background-repeat: no-repeat;
background-size: 100% 2px;
background-position: 0 50px, 0 100px;
The new approach of course requires that the browser has to support multi-backgrounds features (looks like just IE8- do not support this cool feature).
So that's almost what you need to solve this problem. Now we need to find how to apply that style using script. We can't select a pseudo-element (or something like that) via script. We can just use the window.getComputedStyle() method to get the read-only style of a pseudo-element. However we always have a way to modify the CSS directly. That's is by using pure JS with the help of document.styleSheets and cssRules. They allow us to insert/remove/modify a rule.
That looks great. But there is still another issue. When changing the style using that method, the style is not applied right (at least it happens to the ::-webkit-scrollbar-track, it may not happen to other elements). Only when you move the mouse over the scrollbar, the new style is applied. I've just found a simple way to invalidate that scrollbar by setting the overflow of document.documentElement (the html) to hidden and set it back to auto. That works almost well.
Here is the code:
var requiredTb = $(".required input");
var invalids = requiredTb;
var offsets = [];
//init offsets to highlight on the trackbar later
requiredTb.each(function() {
offsets.push(($(this).offset().top)/document.body.scrollHeight * 100);
});
//find the rule styling the -webkit-scrollbar-track
//we added in the CSS stylesheet, this is done just 1 time
var sheets = document.styleSheets;
var trackRule;
for(var i = 0; i < sheets.length; i++){
var rules = sheets[i].cssRules || sheets[i].rules;
for(var j = 0; j < rules.length; j++){
var rule = rules[j];
if(rule.selectorText == "::-webkit-scrollbar-track:vertical"){
trackRule = rule; break;
}
}
}
//define an invalidate() method, we need to use this method
//to refresh the scrollbars, otherwise the newly applied style is not affected
window.invalidate = function(){
$(document.documentElement).css('overflow','hidden');
setTimeout(function(e){
$(document.documentElement).css('overflow','auto');
},1);
};
//this is the main function to set style for the scrollbar track.
function setTrackHighlights(positions, color){
positions.sort();//ensure that the input array should be ascendingly sorted.
trackRule.style.cssText = "";
var gradient = "background: ", backPos = "background-position: ";
var winHeight = $(window).height();
$.each(positions, function(i,pos){
gradient += "linear-gradient(" + color + ", " + color + "),";
backPos += "0 " + pos + "%,"
});
gradient = gradient.substr(0,gradient.length-1) + ";";
backPos = backPos.substr(0,backPos.length -1) + ";";
trackRule.style.cssText += gradient + backPos + "background-repeat:no-repeat; background-size:100% 2px";
invalidate();
}
//initially set the highlights on the trackbar
setTrackHighlights(offsets,'red');
//handle the oninput event to update the highlights accordingly
requiredTb.on('input', function(e){
var required = $(this).closest('.required');
var refreshHighlights = false;
if(this.value && !required.is('.ok')) {
required.addClass('ok');
refreshHighlights = true;
invalids = invalids.not(this);
}
if(!this.value && required.is('.ok')) {
required.removeClass('ok');
refreshHighlights = true;
invalids = invalids.add(this);
}
if(refreshHighlights){
offsets.splice(0);
invalids.each(function() {
offsets.push(($(this).offset().top)/document.body.scrollHeight * 100);
});
setTrackHighlights(offsets,'red');
}
});
You have to add an empty ::-webkit-scrollbar-track:vertical rule (we need to deal only with the vertical scrollbar) in the CSS code, it should be appended at the last to override any similar rule before. We can in fact use the insertRule() method (of a CSSRuleList which can be accessed via cssRules property) to add a new rule instead of looping through the styleSheets, and through the cssRules (of each sheet) to find the empty rule ::-webkit-scrollbar-track:vertical.
The code I posted here can be improved, such as you can add another method setTrackHighlights to allow to add more lines (instead of rendering all the lines each time we need to add/remove just 1 line)...
Note that by using the term line, I mean the rendering representation of a match on the trackbar.
Demo
I want to create buttons like this in JavaFX (not html).
http://jsfiddle.net/x7dRU/3/
(hover on them to see the effect)
[Stupid Stackoverflow insists on me posting jsfiddle code here which isn't relevant]
<li>Button 1</li>
So with a rounded border and a transparent background. Unfortunately the background/insets technique seems to overwrite the content from outside to in. So if you draw a bright border, then you can't undo the brightness to create a dark&transparent background without hardcoding the colour. I.e. it's not write once, run everywhere, on different coloured panels.
-fx-border-color doesn't seem to support rounding or at least isn't recommended here Set border size . I imagine the rounding of the border doesn't sync with the rounding of the background.
Seems HTML5 has the edge on this one then. Tell me I'm wrong :-) ...although I suspect my question can't be done without specifying the colour for each and every button context.
Browny points.
Note, I realise I've coloured the white border greenish (context sensitive), I'm happy with a border of semi-transparent white as a solution. First prize would be a burn/dodge/etc(background-colour) function ala photoshop.
Plan B.
It doesn't look so bad without rounded edges, so maybe I should just resort to -fx-border-color
Background info
Have a look at the information in the css documentation on "looked-up colors"(scroll down a little, beyond the named color section).
The way these basically work, is that you can define a "looked up color" (i.e. a color-valued variable) and apply it to a node in the scene graph. The value is inherited by any node descended from that node in the scene graph.
Now have a browse through the default style sheet, modena.css. The way this works is that almost everything is defined in terms of a very short list of looked-up colors. The consequence is that you can readily "theme" your applications just by redefining those colors on the root of the scene. Try adding this stylesheet to your favorite JavaFX application:
.root {
-fx-base: #c7dec7;
-fx-accent: #00c996 ;
-fx-default-button: #abedd8 ;
-fx-focus-color: #03d39e;
-fx-faint-focus-color: #03d39e22;
}
As you've observed, -fx-border is not used at all in the default stylesheet; instead borders are implemented by defining "nested" background colors which are essentially rectangular fills laid on top of each other. This is apparently more efficient (quite considerably so, by my understanding). So you are correct that making the inner of two backgrounds transparent will simply reveal the "outer" border color, not the color of the background.
How to achieve what you're looking for
The background of a pane defaults to the looked-up color -fx-background, which in turn defaults to a lighter version of -fx-base. So if you stick to changing the color of the pane containing the buttons by changing -fx-background or -fx-base, then you can make the button appear transparent by setting its background to
-fx-background-color: (some-border-color), -fx-background ;
The default borders for buttons contain three values; -fx-shadow-highlight, -fx-outer-border, and -fx-inner-border. You could override the values for these individually, or just redefine the background color as you need.
An approximation to what you want is in this example: you can mess with the exact values for the thickness of the border (from the second -fx-background-insets value) and the radius of the corners to get it as you need. If you want to get fancy with it, play with combinations of ladders and gradients.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class StyledButtonExample extends Application {
private int currentColorIndex = 0 ;
private final String[] baseColors = new String[] {"#8ec919", "#bfe7ff", "#e6e6fa",
"#ffcfaf", "#fff7f7", "#3f474f"};
private StackPane root ;
#Override
public void start(Stage primaryStage) {
root = new StackPane();
Button button = new Button("Change colors");
button.setOnAction(event -> changeColors());
root.getChildren().add(button);
Scene scene = new Scene(root,400,400);
scene.getStylesheets().add(getClass().getResource("transparent-button.css").toExternalForm());
root.setStyle("-fx-base: "+baseColors[0]+";");
primaryStage.setScene(scene);
primaryStage.show();
}
private void changeColors() {
currentColorIndex = (currentColorIndex + 1) % baseColors.length ;
root.setStyle("-fx-base: "+baseColors[currentColorIndex]+";");
}
public static void main(String[] args) {
launch(args);
}
}
transparent-button.css:
.button {
-fx-background-color: derive(-fx-base, 60%), -fx-background ;
-fx-background-insets: 0, 1px ;
-fx-background-radius: 4px, 0 ;
}
.button:hover {
-fx-background-color: #fff, derive(-fx-background, -5%) ;
}
I have a FocusPanel that when gets clicked adds new Elements/Widgets to itself making its height increase. Note, that there is no explicit change of height in the css of the FocusPanel, the height just increases as the result of adding new elements inside the panel
I would like that increase in height to occur through a smooth transition but I am not sure how to achieve it.
I tried applying css transition: height 2s; to the FocusPanel and also to all the other Elements/Widgets that I add to it. But it does not seem to work, there is no transition at all. I assume it is because the height does not increase as a result of me changing the css property but rather by just adding more elements to a container.
What is the right way to achieve a smooth transition of height when adding new elements programatically to a Panel? Thanks!
PS. A good example of what I would like to achieve is the way twitter handles the transition of the panel height when one clicks on a twit.
CSS animations only work if you set the height to fixed values.
A way is to create your own implementation of panel, and override the add method, so as it takes care of computing height and setting it before and after animation time.
As #fascynacja points in its comment, I would go with gwtquery to do that, because of different reasons, but main one is that it is a lightweight library developed in gwt which allows you doing a lot of things with few code lines.
Here you have an example of a panel doing what you want using gquery animations.
import static com.google.gwt.query.client.GQuery.*;
[...]
// Create your own implementation of a panel
public static class MyFlowPanel extends FlowPanel {
// The GQuery object for this panel
GQuery $this = $(this);
// Override the add method so as each time it is called, we run an animation
// You can do the same with the remove method.
#Override
public void add(Widget w) {
// Compute the actual height
int hInitial = $this.height();
// Set height to auto before adding the new child.
$this.height("auto");
// Add the new widget to panel
super.add(w);
// Compute the new height
int hFinal = $this.height();
// Use Gquery to .animate the panel from the old to the new height
// You could replace this with css3 transitions
$this.height(hInitial)
.stop(true)
.animate("height: " + hFinal, 2000);
};
};
public void onModuleLoad() {
// Create your panel, and use it as usual in GWT
final FlowPanel myFlowPanel = new MyFlowPanel();
RootPanel.get().add(myFlowPanel);
// Set some css properties to your panel. You could set these in your style-sheet.
$(myFlowPanel).css($$("border: 1px solid grey; border-radius: 8px; background: #F5FFFA; width: 500px; padding: 8px"));
// Add 10 labels to the panel in periodes of 1000 ms
Scheduler.get().scheduleFixedPeriod(new RepeatingCommand() {
int c = 10;
public boolean execute() {
if (c-- > 0) {
myFlowPanel.add(new Label(c + " Lorem ipsum dolor sit amet, consectetur adipiscing elit."));
return true;
}
return false;
}
}, 1000);
}
Here is a strange problem I am having, althougt I have a work around I would still like to understand why it is happening.
Here is my Composite class
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.*;
public class ExpandingOvalButton extends Composite implements HasClickHandlers
{
private PushButton button;
private Label label = new Label();
private AbsolutePanel panel = new AbsolutePanel();
public ExpandingOvalButton(String text)
{
int width = 40;
initWidget( panel );
Image image = new Image( "icons/ovalBlueButton.png");
label.setText(text);
width = width + (text.length() * 8);
String widthStr = width + "px";
image.setWidth(widthStr);
image.setHeight("50px");
button = new PushButton( image );
button.setWidth(widthStr);
button.setHeight("50px");
panel.setSize(widthStr, "50px");
panel.add(button, 0, 0);
panel.add(label, 18, 14);
}
...
It works perfectly for me when I add it to a flex table along with other widget and then I can apply css styles to the flextable and position it fine. Problem is if I want to apply css positioning to a ExpandingOvalButton instance which does not sit inside of an other panel it does not work properly.
private ExpandingOvalButton startBtn = new ExpandingOvalButton("Start");
startBtn .getElement().setId("myId");
#myId
{
position: absolute;
left: 30%;
top: 120px;
}
This will NOT position itself 30% from the left side.
However if I add startBtn to a Panel and then apply the same CSS style to the Panel it WILL position itself 30% from the left.
panel.add(startBtn);
panel.getElement().setId("myId");
Anyone come across this before or know what might be causing it?
If you look at the source code for AbsolutePanel, you will find this in the constructor:
DOM.setStyleAttribute(getElement(), "position", "relative");
This has priority over the styles declared in your css. You can force it to use absolute positioning by doing something similar:
DOM.setStyleAttribute(startBtn.getElement(), "position", "absolute");