Share
Embed
From Java to Kotlin Multiplatform - DevFest Nuremberg
Jossi Wolf
December 14, 2019
Transcript
81 slides
-
@jossiwolf internal fun calcRangeTicks() { var dx = (actual_maxx -
actual_minx).toDouble() var dy = (actual_maxy - actual_miny).toDouble() val sw = getWidth() val sh = getHeight() val border = 1.09345 if (Math.abs(last_minx - actual_minx) + Math.abs(last_maxx - actual_maxx) > 0.1 * (actual_maxx - actual_minx)) { mTickX = calcTick(sw, dx).toFloat() dx = mTickX * Math.ceil(border * dx / mTickX) var tx = (actual_minx + actual_maxx - dx) / 2 tx = mTickX * Math.floor(tx / mTickX) minx = tx.toFloat() tx = (actual_minx.toDouble() + actual_maxx.toDouble() + dx) / 2 tx = mTickX * Math.ceil(tx / mTickX) maxx = tx.toFloat() last_minx = actual_minx last_maxx = actual_maxx } if (Math.abs(last_miny - actual_miny) + Math.abs(last_maxy - actual_maxy) > 0.1 * (actual_maxy - actual_miny)) { mTickY = calcTick(sh, dy).toFloat() dy = mTickY * Math.ceil(border * dy / mTickY) var ty = (actual_miny + actual_maxy - dy) / 2 ty = mTickY * Math.floor(ty / mTickY) miny = ty.toFloat() ty = (actual_miny.toDouble() + actual_maxy.toDouble() + dy) / 2 ty = mTickY * Math.ceil(ty / mTickY) maxy = ty.toFloat() last_miny = actual_miny last_maxy = actual_maxy } } -
@jossiwolf //JVM actual val platform = "JVM" //JS actual val
platform = "JS" -
@jossiwolf expect val platform: String fun main() { println("Running on
$platform") } //JVM actual val platform = "JVM" //JS actual val platform = "JS" -
@jossiwolf $ gradle compileJS $ "Running on JS" $ gradle
compileJVM $ "Running on JVM" -
@jossiwolf expect interface JPanel class CycleEdit extends JPanel //Common Module
No actual declaration in JVM/JS found. -
@jossiwolf expect interface JPanel class CycleEdit extends JPanel //Common Module
No actual declaration in JS found. -
@jossiwolf //JS actual class JFrame(val title: String? = null) {
fun draw() = ... } -
@jossiwolf abstract class Interpolator { static final int SPLINE =
0; static final int LINEAR = 1; abstract void getPos(double t, double[] v); abstract void getPos(double t, float[] v); abstract double getPos(double t, int j); abstract void getSlope(double t, double[] v); abstract double getSlope(double t, int j); abstract double[] getTimePoints(); } -
@jossiwolf abstract class Interpolator { abstract val timePoints: DoubleArray abstract
fun getPos(t: Double, v: DoubleArray) abstract fun getPos(t: Double, v: FloatArray) abstract fun getPos(t: Double, j: Int): Double abstract fun getSlope(t: Double, v: DoubleArray) abstract fun getSlope(t: Double, j: Int): Double } -
@jossiwolf private fun getP(time: Double): Double { var index =
mPosition.binarySearch(time) var p = 0.0 if (index > 0) { p = 1.0 } else if (index != 0) { index = -index - 1 val m = (mPeriod[index] - mPeriod[index - 1]) / (mPosition[index] - mPosition[index - 1]) p = (mArea[index - 1] + (mPeriod[index - 1] - m * mPosition[index - 1]) * (time - mPosition[index - 1]) + m * (time * time - mPosition[index - 1] * mPosition[index - 1]) / 2) } return p } -
@jossiwolf private fun getP(time: Double): Double { val index =
mPosition.binarySearch(time) return when (index > 0) { true -> 1.0 false -> { val searchIndex = -index - 1 val acceleration = calculateAcceleration(searchIndex) return getArea(acceleration) } } } -
@jossiwolf If you have messy code, now is the time
to clean it up! -
@jossiwolf public class MainPanel extends JFrame { JTextArea myXmlOutput =
new JTextArea(); JScrollPane myXmlScrollPane = new JScrollPane(myXmlOutput); JButton myPlayButton = new JButton("Play"); JComboBox<String> baseMovement = new JComboBox<>(AnimationPanel.MOVE_NAMES); JComboBox<String> duration = new JComboBox<>(AnimationPanel.DURATION); JComboBox<String> easing = new JComboBox<>(AnimationPanel.EASING_OPTIONS); JPanel main = new JPanel(new BorderLayout(5,5)); JPanel main1 = new JPanel(new BorderLayout(5,5)); JPanel main2 = new JPanel(new BorderLayout(5,5)); GridLayout myGraphLayout= new GridLayout(1, 1); JPanel myGraphs = new JPanel(myGraphLayout); JMenuBar topMenu= new JMenuBar(); JPanel base = new JPanel(new BorderLayout()); JTabbedPane myCycleEditTabs = new JTabbedPane(); CycleSetModel myCycleSetModel = new CycleSetModel(myXmlOutput); private CycleSetModel.Cycle createCycle() { return myCycleSetModel.createCycle(); } AnimationPanel animationPanel = new AnimationPanel(myCycleSetModel, myPlayButton); public static JButtoncreateTabbButton(String text) { JButton ret = new JButton(text); ret.setBorder(null); if (text == null) { ret.setIcon(UIManager.getIcon("InternalFrame.paletteCloseIcon")); } ret.setFocusPainted(false); ret.setContentAreaFilled(false); ret.setBorderPainted(true); ret.setBackground(null); ret.setHorizontalTextPosition(SwingConstants.LEFT); ret.setMargin(new Insets(0, 0, 0, 0)); return ret; } MainPanel() { super("Cycle Editor"); setBounds(new Rectangle(1000, 900)); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); myCycleSetModel.myMainPanel = this; CycleSetModel.CyclemyCycle; CycleEdit cycleEdit; myCycle = createCycle(); myGraphs.add(myCycle.myView); main.setBorder(new EmptyBorder(new Insets(5, 5, 5, 5))); main.add(main1,BorderLayout.CENTER); main.add(main2,BorderLayout.SOUTH); main2.add(animationPanel,BorderLayout.CENTER); main1.add(myGraphs,BorderLayout.CENTER); main2.add(myXmlScrollPane,BorderLayout.EAST); main1.add(base,BorderLayout.EAST); myXmlScrollPane.setPreferredSize(new Dimension(100, 300)); BasicArrowButtonnext = new BasicArrowButton(BasicArrowButton.EAST); BasicArrowButtonprev = new BasicArrowButton(BasicArrowButton.WEST); myCycle.myModel.delete= next; // add the first panel cycleEdit = new CycleEdit(myCycle.myView, myCycle.myModel, animationPanel); myCycle.myControl = cycleEdit; JScrollPane scrollPane = new JScrollPane(cycleEdit); myCycleEditTabs.add(scrollPane); cycleEdit.updateTabName(myCycle.myModel.getAttName()); // add the add more panel myCycleEditTabs.add(new JPanel(), "+"); JButton newTabbButton = createTabbButton("+"); newTabbButton.addActionListener(e -> createNewCycle()); myCycleEditTabs.setTabComponentAt(1, newTabbButton); base.add(myCycleEditTabs); JPanel bottomControls = new JPanel(); base.add(bottomControls, BorderLayout.SOUTH); myPlayButton.setText("XXXXX"); myPlayButton.setPreferredSize(myPlayButton.getPreferredSize()); myPlayButton.setText("Play"); bottomControls.add(myPlayButton); baseMovement .addActionListener((e) -> animationPanel.setMovement(baseMovement.getSelectedIndex())); bottomControls.add(baseMovement); duration.setSelectedIndex(AnimationPanel.DURATION.length - 1); duration.addActionListener((e) -> animationPanel.setDurationIndex(duration.getSelectedIndex())); bottomControls.add(duration); easing.addActionListener((e) -> animationPanel.setEasing((String) easing.getSelectedItem())); bottomControls.add(easing); myXmlScrollPane.setPreferredSize(base.getPreferredSize()); setContentPane(main); validate(); myCycle.myModel.update(); JMenu menu = new JMenu("File"); topMenu.add(menu); JMenuItem menuItem = new JMenuItem("parse xml", KeyEvent.VK_T); menuItem.addActionListener(e -> myCycle.myModelSet.parse()); menu.add(menuItem); menu = new JMenu("Examples"); topMenu.add(menu); for (int i = 0; i < KeyCycleExamples.all.length; i++) { String text = KeyCycleExamples.all[i][1]; int speed = Integer.parseInt(KeyCycleExamples.all[i][2]); int movement = Integer.parseInt(KeyCycleExamples.all[i][3]); int easingType = Integer.parseInt(KeyCycleExamples.all[i][4]); menuItem = new JMenuItem(KeyCycleExamples.all[i][0]); menuItem.addActionListener(e -> { myXmlOutput.setText(text); duration.setSelectedIndex(speed); baseMovement.setSelectedIndex(movement); easing.setSelectedIndex(easingType); myCycle.myModelSet.parse(); animationPanel.play(); }); menu.add(menuItem); } menu = new JMenu("Cycle"); menuItem = new JMenuItem("Add cycle", KeyEvent.VK_A); menuItem.addActionListener(e -> createNewCycle()); menu.add(menuItem); menuItem = new JMenuItem("Remove cycle", KeyEvent.VK_R); menuItem.addActionListener(e -> removeCurrentCycle()); menu.add(menuItem); topMenu.add(menu); menuItem = new JMenuItem("Play", KeyEvent.VK_P); menuItem.addActionListener(e -> animationPanel.play()); topMenu.add(menuItem); this.setJMenuBar(topMenu); } public CycleSetModel.CyclecreateNewCycle() { CycleSetModel.Cyclecycle = createCycle(); CycleEdit cycleEdit = new CycleEdit(cycle.myView, cycle.myModel, animationPanel); cycle.myControl = cycleEdit; cycleEdit.setRemoveCallback(e -> removeCycle(cycle)); int count = myCycleEditTabs.getTabCount(); myCycleEditTabs.insertTab("label", null, cycleEdit, "tooltip", count- 1); cycleEdit.updateTabName(cycle.myModel.getAttName()); myGraphs.add(cycle.myView); myGraphLayout.setRows(myCycleEditTabs.getTabCount() - 1); myGraphs.validate(); return cycle; } public static int getTabbIndex(JComponentcomponent) { Container tabb = component.getParent(); Component lastComponent = component; while (!(tabb instanceof JTabbedPane)) { lastComponent = tabb; tabb = tabb.getParent(); } return ((JTabbedPane) tabb).indexOfComponent(lastComponent); } void removeCurrentCycle() { int i = myCycleEditTabs.getSelectedIndex(); removeCycle(myCycleSetModel.myCycles.get(i)); } void removeCycle(CycleSetModel.Cycle cycle) { if (myCycleEditTabs.getTabCount() < 3) { // cant remove the last one return; } if (cycle.myControl != null) { myCycleEditTabs.remove(getTabbIndex(cycle.myControl)); } myGraphs.remove(cycle.myView); myGraphs.validate(); myGraphLayout.setRows(myCycleEditTabs.getTabCount() - 1); myCycleSetModel.removeCycle(cycle); myCycleEditTabs.setSelectedIndex(0); } public static void main(String[] arg) { MainPanel f = new MainPanel(); f.setVisible(true); } } -
@jossiwolf class CycleModel { public void addActionListener(ActionListener listener) public String[]
getStrings() public void delete() public void add() public void setCycle(CycleView cycleView) public void update() public float getComputedValue(float v) public void setDot(float x, float y) public void setPos() public void setPeriod() public void setAmp() public void setOffset() void setMode() public void setSelected(int selectedIndex) public void setUIElements(JSlider pos, JSlider period, JSlider amp, JSlider off, JComboBox<String> mode) void updateUIelements() public void changeSelection(int delta) class ParseResults { void add() } public static String trimDp(String v) public void parseXML(String str) public String getKeyFrames() public void generateXML() String getAttName() public void setAttr(int selectedIndex) public void selectClosest(Point2D p) public void setTarget(JTextField target) } -
@jossiwolf class CycleModel { public void addActionListener(ActionListener listener) public String[]
getStrings() public void delete() public void add() public void setCycle(CycleView cycleView) public void update() public float getComputedValue(float v) public void setDot(float x, float y) public void setPos() public void setPeriod() public void setAmp() public void setOffset() void setMode() public void setSelected(int selectedIndex) public void setUIElements(…) void updateUIelements() public void changeSelection(int delta) class ParseResults { void add() } public static String trimDp(String v) public void parseXML(String str) public String getKeyFrames() public void generateXML() String getAttName() public void setAttr(int selectedIndex) public void selectClosest(Point2D p) public void setTarget(JTextField target) } -
@jossiwolf class CycleModel { final int POS = 0; final
int PERIOD = 1; final int AMP = 2; final int OFFSET = 3; public int selected = 3; ActionListener listener; CycleView mCycleView; CycleSetModel.Cycle myCycle; public JTextField mKeyCycleNo; private JComboBox<String> mMode; JButton delete, add; public JSlider mPos, mPeriod, mAmp, mOff; ... } -
@jossiwolf class CycleSetModel { MainPanel myMainPanel; JTextArea myXmlOutput; static class
Cycle { CycleSetModel myModelSet; CycleModel myModel; CycleView myView; CycleEdit myControl; Cycle(CycleSetModel set) { myModelSet = set; myView = new CycleView(this); myModel = new CycleModel(this); } } CycleSetModel(JTextArea xmlOutput) { myXmlOutput = xmlOutput; } -
@jossiwolf If you have messy code, now is the time
to clean it up! -
@jossiwolf class CycleModel { public void addActionListener(ActionListener listener) public String[]
getStrings() public void delete() public void add() public void setCycle(CycleView cycleView) public void update() public float getComputedValue(float v) public void setDot(float x, float y) public void setPos() public void setPeriod() public void setAmp() public void setOffset() void setMode() public void setSelected(int selectedIndex) public void setUIElements(JSlider pos, JSlider period, JSlider amp, JSlider off, JComboBox<String> mode) void updateUIelements() public void changeSelection(int delta) class ParseResults { void add() } public static String trimDp(String v) public void parseXML(String str) public String getKeyFrames() public void generateXML() String getAttName() public void setAttr(int selectedIndex) public void selectClosest(Point2D p) public void setTarget(JTextField target) } -
@jossiwolf public void parseXML(String str) { try { InputStream inputStream
= new ByteArrayInputStream(str.getBytes(Charset.forName("UTF-8"))); SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser saxParser = factory.newSAXParser(); ParseResults results = new ParseResults(); saxParser.parse(inputStream, new DefaultHandler(){ ... }); values = results.values; selected = values[POS].length / 2; mMode.setSelectedIndex(results.shape); mTarget.setText(results.target); mAttrIndex = results.valueType.ordinal(); update(); } catch (ParserConfigurationException e) { ... } } -
@jossiwolf class CycleModel(val cycle: Cycle) { fun getKeyFrames(): List<KeyFrame> =
... fun selectClosest(toPoint: Point2D) = ... } -
@jossiwolf # Don‘t convert UI & Repo # Free your
Presentation layer of UI dependencies -
@jossiwolf fun updateUIelements() { inCallBack = true val max =
calculateMax() val min = calculateMin() val middle = max / 2 mPos.setEnabled(middle) delete!!.setEnabled(middle) } -
@jossiwolf fun updateUIelements() { inCallBack = true val max =
calculateMax() val min = calculateMin() val middle = max / 2 mPos.setEnabled(middle) delete!!.setEnabled(middle) } -
@jossiwolf data class CycleViewState( val isMedian: Boolean, val positionMin: Int,
val positionMax: Int ) -
@jossiwolf suspend fun updateUIElements() { val min = calculateMin() val
max = calculateMax() val isMiddle = ... val state = CycleViewState(isMiddle, min, max) stateFlow.emit(state) } -
@jossiwolf class CycleModel { // Couroutines are available in common
modules val stateFlow = flow<CycleViewState> { … } } -
@jossiwolf class CycleActivity: AppCompatActivity() { private val cycleModel by lazy
{ CycleModel() } override fun onCreate() { cycleModel.stateFlow.collect(::render) } private suspend fun render(state: CycleViewState) { … } } -
@jossiwolf # Introduce a View State # Make your data
flow unidirectional # Don‘t have hard coupling between layers