Coder Social home page Coder Social logo

cybernetics / xemantic-kotlin-swing-dsl Goto Github PK

View Code? Open in Web Editor NEW

This project forked from xemantic/xemantic-kotlin-swing-dsl

0.0 2.0 0.0 136 KB

Express your swing code easily in kotlin

License: GNU Lesser General Public License v3.0

Kotlin 86.62% Java 13.38%

xemantic-kotlin-swing-dsl's Introduction

xemantic-kotlin-swing-dsl

Express your Swing code easily in Kotlin

This project was born when I had an urgent need to quickly provide a simple UI for remote JVM application. I needed a remote control for my Robot, talking over OSC protocol, already coded in Kotlin and based on functional reactive programming principles (See we-are-the-robots on GitHub). I decided to go for Swing, to stay in the same ecosystem, but I remember the experience of coding GUI in Swing years ago, and I remember what I liked about it and what was painful. Fortunately now I also have the experience of building Domain Specific Languages in Kotlin and I quickly realized that I can finally swing the way I always wanted to.

Example

import com.badoo.reaktive.observable.*
import com.badoo.reaktive.scheduler.ioScheduler
import java.awt.Dimension
import javax.swing.SwingConstants

fun main() = mainFrame("My Browser") {
  val contentBox = label("") {
    horizontalAlignment = SwingConstants.CENTER
    verticalAlignment = SwingConstants.CENTER
    preferredSize = Dimension(300, 300)
  }
  val urlBox = textField(10)
  val goAction = button("Go!") {
    isEnabled = false
    actionEvents
      .withLatestFrom(urlBox.textChanges) { _, url: String -> url }
      .doOnAfterNext { url ->
        isEnabled = false
        contentBox.text = "Loading: $url"
      }
      .delay(1000, ioScheduler) // e.g. REST request
      .observeOn(swingScheduler)
      .subscribe { url ->
        isEnabled = true
        contentBox.text = "Ready: $url"
      }
  }
  urlBox.textChanges.subscribe { url ->
    goAction.isEnabled = url.isNotBlank()
    contentBox.text = "Will load: $url"
  }
  contentPane = borderPanel {
    layout.hgap = 4
    panel.border = emptyBorder(4)
    north = borderPanel {
      west = label("URL")
      center = urlBox
      east = goAction
    }
    center = contentBox
  }
}

will produce:

example app image

Benefits:

  • compact code, minimal verbosity
  • declarative instead of imperative
  • functional reactive programming way of handling events (Reaktive)
  • component encapsulation - communication through well defined event streams
  • swingScheduler for receiving asynchronously produced events (see below)

And here is an equivalent code in Java for the sake of comparison:

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.lang.reflect.InvocationTargetException;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

public class SwingJavaWay {

  public static void main(String[] args) throws InvocationTargetException, InterruptedException {
    SwingUtilities.invokeAndWait(SwingJavaWay::createFrame);
  }

  private static void createFrame() {

    JFrame frame = new JFrame("My Browser");

    final JLabel contentBox = new JLabel();
    contentBox.setHorizontalAlignment(SwingConstants.CENTER);
    contentBox.setVerticalAlignment(SwingConstants.CENTER);
    contentBox.setPreferredSize(new Dimension(300, 300));

    JPanel contentPanel = new JPanel(new BorderLayout());

    JPanel northContent = new JPanel(new BorderLayout(4, 0));
    northContent.add(new JLabel(("URL")), BorderLayout.WEST);
    northContent.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));

    JTextField urlBox = new JTextField(10);
    JButton goAction = new JButton("Go!");
    goAction.setEnabled(false);

    goAction.addActionListener(
        e -> {
          contentBox.setText("Loading: " + urlBox.getText());
          goAction.setEnabled(false);
          Timer timer =
              new Timer(
                  1000,
                  t -> {
                    contentBox.setText("Ready: " + urlBox.getText());
                    goAction.setEnabled(true);
                  });
          timer.setRepeats(false);
          timer.start();
        });
    northContent.add(goAction, BorderLayout.EAST);

    urlBox
        .getDocument()
        .addDocumentListener(
            new DocumentListener() {
              @Override
              public void insertUpdate(DocumentEvent e) {
                fireChange(e);
              }

              @Override
              public void removeUpdate(DocumentEvent e) {
                fireChange(e);
              }

              @Override
              public void changedUpdate(DocumentEvent e) {
                fireChange(e);
              }

              private void fireChange(DocumentEvent e) {
                String text = urlBox.getText();
                contentBox.setText("Will load: " + text);
                goAction.setEnabled(!text.trim().isEmpty());
              }
            });

    northContent.add(urlBox, BorderLayout.CENTER);
    contentPanel.add(northContent, BorderLayout.NORTH);
    contentPanel.add(contentBox, BorderLayout.CENTER);

    frame.setContentPane(contentPanel);
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    frame.pack();
    frame.setVisible(true);
  }
}

ℹī¸ There is also another more elaborate Kotlin example: SwingKotlinWayWithReactiveModelViewPresenter , showing how to use Model-View-Presenter pattern with this library, where events from the view are represented as reactive Observables.

Swing scheduler

By default everything in Swing is supposed to run on the same thread. Any update to any GUI component should happen there, otherwise concurrency might cause consistency issues. But in many cases long running computation, or asynchronous IO, will require us to receive results in one thread and display them in the main Swing thread. This is what swingScheduler is for:

fun main() = mainFrame("swingScheduler example") {
  contentPane = label{
    observableInterval(1000, swingScheduler)
      .subscribe { tick ->
        text = tick.toString()
      }
    preferredSize = Dimension(100, 100)
    horizontalAlignment = SwingConstants.CENTER
  }
}

The observableInterval creates a constant stream of ticks produced by another thread. Once they happen, the subscription code will be handled by the Swing thread. Most of the time it is not needed to specify swingScheduler for simple event handling, because the default scheduler for receiving events will be usually the same as the one used for publishing them, and this one is already the Swing event thread.

xemantic-kotlin-swing-dsl's People

Contributors

morisil avatar

Watchers

 avatar  avatar

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.