18/07/2014

Adicionando estilo à sua aplicação com CSS

Até o momento nesse blog fizemos aplicações e deixamos a "cara" delas a padrão que vem com o JavaFX, ou seja, não nos preocupamos em mudar a aparência das da nossa aplicação. Para fazer isso no JavaFX, não precisamos escrever código Java, mas sim conhecer CSS (Cascade Style Sheet), onde podemos declarativamente configurar a aparência de nossa aplicação.

O que é e o que pode ser feito com CSS?

Com o CSS do javaFX é possível adicionar efeitos, mudar cores, dimensionar e completamente trocar a aparência da aplicação.  É possível também definir a aparência quando o mouse fica sobre o elemento.
CSS é uma linguagem declarativa onde identificamos os elementos da nossa aplicação e em seguida definimos valores para as propriedades suportadas para aquele componente.
Essa tecnologia já é utilizada em páginas WEB e para o JavaFX todas as possibilidades de uso do CSS estão descritas em um completo guia de referência.

Como referênciar os componentes do JavaFX no CSS?

Os componentes javafx podem ter várias classes CSS que referenciamos para usarmos na declaração do CSS. Assim, no código Java falamos qual a classe daquele componente e no CSS referenciamos a classe com ponto(".").  Também podemos dar um id para nosso componente e referenciar a classe dele usando cerquilha ("#"). Note que o ID deve ser único, já a classe pode ser aplicada a diversos nós.

Como carregar CSS no JavaFX?

O CSS pode ficar em um arquivo separado e ser carregado na Scene da aplicação ou podemos adicionar estilo a qualquer classe que estenda de (todas as classes de uma cena no JavaFX) usando o método setStyle. 

Exemplo

Criamos a seguinte aplicação de exemplo para que você possa ver o que abordamos anteriormente na prática.



Note que a aplicação está diferente. Isso é por que eu carreguei o arquivo app.css na cena:

cena.getStylesheets().add("app.css");

Esse arquivo configura o estilo raiz de toda a aplicação, veja o conteúdo:

.root{
    -fx-base: darkblue;
    -fx-background: lightblue;
}
.button {
    -fx-font-weight: bold;
    -fx-font-size: 15px;
}

.label {
    -fx-font-style: italic;
    -fx-font-size: 9px;
    -fx-text-fill: darkgreen;
}

.titulo {
    -fx-font-style: normal;
    -fx-font-weight: bolder;
    -fx-font-size: 30px;
    -fx-alignment: center;
}

Também temos contéudos para o label  lblTitulo que têm a classe titulo, que referenciamos para modificar o estilo:

No código Java:
 lblTitulo.getStyleClass().add("titulo");

No CSS:
.titulo {
    -fx-font-style: normal;
    -fx-font-weight: bolder;
    -fx-font-size: 30px;
    -fx-alignment: center;
}

Notem que alguns componentes já tem uma classe usado pelo próprio JavaFX, mas que você pode modificar como quiser. No lado esquerdo podemos adicionar o CSS que queremos e ao apertar o botão, esse CSS é aplicação no Text do lado direito. 

btnAplicar.setOnAction( event -> {
    txtAlvo.setStyle(txtCSS.getText());
});



Veja o código inteiro da nossa aplicação abaixo:

// imports omitidos
public class TesteCSS extends Application {

 String txt = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc porta erat id lectus interdum, a pharetra est luctus. Nulla interdum convallis molestie. In hac habitasse platea dictumst. Ut ullamcorper ultricies viverra. Quisque blandit libero in ante sagittis sagittis. Ut gravida nibh quis justo sodales rutrum. Fusce euismod diam diam, vitae vulputate urna placerat vel. ";
 
 public void start(Stage s) {
  // declarações
  BorderPane raiz = new BorderPane();
  HBox pnlCentro = new HBox(50);
  VBox vbEsquerda = new VBox(10);
  VBox vbDireita = new VBox(10);
  Button btnAplicar = new Button("Aplicar CSS");
  TextArea txtCSS = new TextArea();
  Text txtAlvo = new Text(txt);
  Label lblTitulo = new Label("Testando CSS");
  Scene cena = new Scene(raiz, 800, 250);

  // configurando Layout e adicionando componentes
  vbEsquerda.getChildren().addAll(new Label("Entre o CSS aqui"), txtCSS);
  vbDireita.getChildren().addAll(new Label("O texto Alvo"), txtAlvo);

  pnlCentro.getChildren().addAll(vbEsquerda, btnAplicar, vbDireita);
  pnlCentro.setAlignment(Pos.CENTER);

  raiz.setCenter(pnlCentro);
  raiz.setTop(lblTitulo);
  BorderPane.setAlignment(lblTitulo, Pos.CENTER); 

  txtCSS.setMinWidth(350);
  txtAlvo.setWrappingWidth(220);
  btnAplicar.setMinWidth(120);
  btnAplicar.setOnAction( event -> {
   txtAlvo.setStyle(txtCSS.getText());
  });
  // configuramos classes para os nossos labels especiais 
  lblTitulo.getStyleClass().add("titulo");
  // informamos o arquivo principal de CSS 
  cena.getStylesheets().add("app.css");
  s.setScene(cena);
  s.setTitle("Teste de CSS do JavaFX");
  s.show();
 }
}

Como fica se quisermos aplicar CSS:


Conclusão

Nesse artigo mostramos como é fácil mudar a aparência de nossas aplicaçõe usando JavaFX.



25/06/2014

[Especial] Outra app da Copa do Mundo... Usando JavaFX, FXML, Javascript e CSS

Olá pessoal, essa é uma postagem especial da copa do mundo onde faço uma tradução da minha postagem no blog fxapps.blogspot.com

Outra app da Copa do Mundo... Usando JavaFX, FXML, Javascript e CSS

A  Copa do mundo Brasil está acontecendo! É empolgante ver pessoas de todas as partes do mundo visitando nosso pais. Hoje eu decidi criar outra app para a Copa do Mundo, usando JavaFX, mas dessa vez eu não irei escrever nenhum código Java.
É uma aplicação muito simples para visualizar todas as partidas da copa do mundo e quando você clica em um jogo, você tem detalhes do mesmo. Eu gastei menos de 3 horas trabalhando no "core" da aplicação e algumas horas mais trabalhando nos detalhes.

Adquirindo os recursos para criar a aplicação

Recursos quer dizer imagens and para adquiri elas eu baixei todas as imagens de bandeiras do site da Fifa. Fiz um pequeno e fácil scrapping. Note que todas as imagens seguem o seguinte padrão de URL: http://img.fifa.com/images/flags/4/{CODE}.png. Então, para baixar todas as bandeiras, fiz o seguinte script python:

import urllib2
codes = open('countries_codes.txt', 'r')
for line in codes:
        code = line.replace('\n', '').lower() + '.png'
        img_url = 'http://img.fifa.com/images/flags/4/' + code
        print img_url
        req = urllib2.Request(img_url)
        response = urllib2.urlopen(req)
        img = response.read()
        f = open(code, 'w')
        f.write(img)

Também utilizei a seguinte imagem como uma inspiração para nossa aplicação:


A view da aplicação

A view foi inteiramente criada no Scene Builder e foi dado estilos e id para os componentes da view, então, do Javascript nós podemos destacar algumas partes de acordo com as informações da copa.


Temos todos os jogos mostrados na aplicação e quando o usuários clica em um jogo, os detalhes do mesmo são mostrados em um painel:


O estilo da aplicação

Para mudarmos a aplicação para termos as cores do Brasil, usamos um simples arquivo CSS que também troca a aparência da partida quando passamos sobre ela:

.root{
        -fx-base: CornflowerBlue;
        -fx-background: rgb(227,231,239);
}
.TitledPane{
        -fx-text-fill: FIREBRICK;
}
#match_details{
        -fx-background-color: rgb(177,174,218);
}
.match:hover{
        -fx-font: bold 12pt "Calibri";
        -fx-effect: dropshadow(three-pass-box , blue, 20, 0.4 , 0 , 0 );
        -fx-cursor: hand;
}

A lógica

Como essa é uma aplicação temporal, não precisamos criar classes de modelo para representar os dados do jogo, então usamos somente Javascript para escrever a lógica da aplicação!
Uma boa alma raspou os dados da FIFA e disponibiliza eles em JSON. Nós lemos esse JSON e basicamente preenchemos a view com os dados que nele vem. Notem que para cada partida eu dei um ID(isso foi chato bagario) e do JSON que peguei, eu preencho os dados. Veja:

var matches = downloadMatchesInfo()
...
function downloadMatchesInfo(){
        print("Trying to download the matches information.........")
        var out, scanner
        try{
                var urlStream = new java.net.URL(MATCHES_DATA_URL).openStream()
                scanner = new java.util.Scanner(urlStream, "UTF-8")
                // TODO: save the latest downloaded JSON
        }catch(e){
                print("Couldn't download the updated information, using a cached one.....")
                scanner = new java.util.Scanner(new java.io.File(CACHED_DATA_URL), "UTF-8")
    
        }    
        scanner.useDelimiter("\\A")
        out = scanner.next();
        scanner.close();
        return eval(out);
}

Usando a função eval, transformamos a nossa String de resposta em um object Javascript para que possamos acessar os dados JSON diretamente. Para entender melhor, veja como as partidas são preenchidas:

function fillMatch(match){
        var viewMatch = $STAGE.scene.lookup("#match_" + match.match_number)
        if(viewMatch && match.home_team.country){
                viewMatch.children[0].image = getImg(match.away_team.code)
                viewMatch.children[1].text = match.status == "future"? "_": match.home_team.goals;
                viewMatch.children[3].text = match.status == "future"? "_": match.away_team.goals;
                viewMatch.children[4].image = getImg(match.home_team.code)
        }
        viewMatch.onMouseClicked = function(e){
                fillMatchDetails(match)
        }
}

function getImg(code){
        var imgUrl = code?"./images/teams/" + code.toLowerCase() + ".png":"./images/no_team.png"
        if(!imgCache[imgUrl])
                imgCache[imgUrl] = new javafx.scene.image.Image(new java.io.FileInputStream(imgUrl))
        return imgCache[imgUrl]

}


Você pode ver no código acima como é fácil usar estruturas do Javascript em um código que lida com bibliotecas Java(veja a variável de cache de imagens chamada imgCache). Notem que quando o usuário clica em uma partida, registramos um listener para preencher os dados da partida em um painel central:

function fillMatchDetails(match){
        var s = $STAGE.scene;
        detailsTransition.playFromStart()
        var notPlayedScore = match.status == "completed"?"0":"_"
        s.lookup("#match_home_team").image = getImg(match.home_team.code)
        s.lookup("#match_away_team").image = getImg(match.away_team.code)
        s.lookup("#match_home_score").text = match.home_team.goals?match.home_team.goals:notPlayedScore
        s.lookup("#match_away_score").text = match.away_team.goals?match.away_team.goals:notPlayedScore
        s.lookup("#match_status").text = "Match " + match.status
        s.lookup("#match_time").text =  match.datetime.substring(0, 16)
        s.lookup("#match_location").text =  match.location
}


Por fim, vejam que invés de injetar os componentes em uma classe controller, decidimos pegar os elementos por ID usando o método lookup da Scene.

Rodando a aplicação

A aplicação não é compilada, é interpretada! Para rodar ela, você simplesmente precisa ter Java 8 instalado e no seu PATH assim você pode rodar a aplicação jjs para rodar a aplicação. Como o código está no github, você pode clonar it e já executar:

$ git clone https://github.com/jesuino/another-world-cup-app.git
$ cd another-world-cup-app
$ jjs -fx app.js
Você também pode editar o arquivo run.sh para configurar a variável JAVA_HOME e fazer o arquivo executável para rodar nossa app:


$ chmod +x run.sh
$ ./run.sh
Se você estiver no windows, minha namorada Luana testou a aplicação nesse "sistema operacional" e ela simplesmente criou um arquivo chamado run.bat com o seguinte conteúdo:
"C:\Program Files\Java\jre8\bin\jjs" -fx app.js


Note que você deve mudar o conteúdo desse arquivo de acordo com sua instalação.

Conclusão

Como você pode ver, é muito fácil criar aplicações JavaFX e não precisamos sequer escrever código Java! Usamos Javascript para ler o JSON e interagir com a view, o que se mostrou uma ótima abordagem, já que não precisamos usar nenhuma biblioteca de terceiros para lidar com o "parsing" de JSON.
Também ensinei minha namorada como rodar e mudar a aparência da aplicação e ela não é uma programadora Java. Isso mostra como aplicações JavaFX são acessíveis a todos os programadores.

A nossa aplicação final: