diff --git a/base/src/commonMain/kotlin/base/event/Key.kt b/base/src/commonMain/kotlin/base/event/Key.kt new file mode 100644 index 00000000000..ab6df2fa786 --- /dev/null +++ b/base/src/commonMain/kotlin/base/event/Key.kt @@ -0,0 +1,85 @@ +package jetbrains.datalore.base.event + +enum class Key private constructor(private val myValue: String) { + A("A"), + B("B"), + C("C"), + D("D"), + E("E"), + F("F"), + G("G"), + H("H"), + I("I"), + J("J"), + K("K"), + L("L"), + M("M"), + N("N"), + O("O"), + P("P"), + Q("Q"), + R("R"), + S("S"), + T("T"), + U("U"), + V("V"), + W("W"), + X("X"), + Y("Y"), + Z("Z"), + DIGIT_0("0"), + DIGIT_1("1"), + DIGIT_2("2"), + DIGIT_3("3"), + DIGIT_4("4"), + DIGIT_5("5"), + DIGIT_6("6"), + DIGIT_7("7"), + DIGIT_8("8"), + DIGIT_9("9"), + LEFT_BRACE("["), + RIGHT_BRACE("]"), + UP("Up"), + DOWN("Down"), + LEFT("Left"), + RIGHT("Right"), + PAGE_UP("Page Up"), + PAGE_DOWN("Page Down"), + ESCAPE("Escape"), + ENTER("Enter"), + HOME("Home"), + END("End"), + TAB("Tab"), + SPACE("Space"), + INSERT("Insert"), + DELETE("Delete"), + BACKSPACE("Backspace"), + EQUALS("Equals"), + BACK_QUOTE("`"), + PLUS("Plus"), + MINUS("Minus"), + SLASH("Slash"), + CONTROL("Ctrl"), + META("Meta"), + ALT("Alt"), + SHIFT("Shift"), + UNKNOWN("?"), + F1("F1"), + F2("F2"), + F3("F3"), + F4("F4"), + F5("F5"), + F6("F6"), + F7("F7"), + F8("F8"), + F9("F9"), + F10("F10"), + F11("F11"), + F12("F12"), + COMMA(","), + PERIOD("."); + + override fun toString(): String { + return myValue + } +} diff --git a/base/src/commonMain/kotlin/base/event/KeyEvent.kt b/base/src/commonMain/kotlin/base/event/KeyEvent.kt new file mode 100644 index 00000000000..ed4e8b96afc --- /dev/null +++ b/base/src/commonMain/kotlin/base/event/KeyEvent.kt @@ -0,0 +1,58 @@ +package jetbrains.datalore.base.event + +class KeyEvent : Event { + + val keyStroke: KeyStroke + val keyChar: Char + + val key: Key + get() = keyStroke.key + + val modifiers: Set + get() = keyStroke.modifiers + + constructor(keyStroke: KeyStroke) { + this.keyStroke = keyStroke + keyChar = 0.toChar() + } + + constructor(key: Key, ch: Char = 0.toChar()) { + keyStroke = KeyStroke(key, emptyList()) + keyChar = ch + } + + constructor(key: Key, ch: Char, modifiers: Collection) { + keyStroke = KeyStroke(key, modifiers) + keyChar = ch + } + + fun `is`(key: Key, vararg modifiers: ModifierKey): Boolean { + return keyStroke.`is`(key, *modifiers) + } + + fun `is`(vararg specs: KeyStrokeSpec): Boolean { + for (s in specs) { + if (s.matches(keyStroke)) return true + } + return false + } + + fun `is`(vararg specs: KeyStroke): Boolean { + for (s in specs) { + if (s.matches(keyStroke)) return true + } + return false + } + + fun has(key: ModifierKey): Boolean { + return keyStroke.has(key) + } + + fun copy(): KeyEvent { + return KeyEvent(key, keyChar, modifiers) + } + + override fun toString(): String { + return keyStroke.toString() + } +} diff --git a/base/src/commonMain/kotlin/base/event/KeyStroke.kt b/base/src/commonMain/kotlin/base/event/KeyStroke.kt new file mode 100644 index 00000000000..45be5dad626 --- /dev/null +++ b/base/src/commonMain/kotlin/base/event/KeyStroke.kt @@ -0,0 +1,46 @@ +package jetbrains.datalore.base.event + +class KeyStroke { + val key: Key + val modifiers: Set + + constructor(key: Key, vararg modifiers: ModifierKey) : this(key, modifiers.asList()) + + constructor(key: Key, modifiers: Collection) { + this.key = key + this.modifiers = HashSet(modifiers) + } + + fun has(key: ModifierKey): Boolean { + return modifiers.contains(key) + } + + fun `is`(key: Key, vararg modifiers: ModifierKey): Boolean { + return matches(KeyStroke(key, *modifiers)) + } + + fun matches(keyStroke: KeyStroke): Boolean { + return equals(keyStroke) + } + + fun with(key: ModifierKey): KeyStroke { + val keys = HashSet(modifiers) + keys.add(key) + return KeyStroke(this.key, keys) + } + + override fun hashCode(): Int { + return key.hashCode() * 31 + modifiers.hashCode() + } + + override fun equals(other: Any?): Boolean { + if (other !is KeyStroke) return false + val otherKeyStroke = other as KeyStroke? + + return key === otherKeyStroke!!.key && modifiers == otherKeyStroke!!.modifiers + } + + override fun toString(): String { + return "$key $modifiers" + } +} \ No newline at end of file diff --git a/base/src/commonMain/kotlin/base/event/KeyStrokeSpec.kt b/base/src/commonMain/kotlin/base/event/KeyStrokeSpec.kt new file mode 100644 index 00000000000..5bd2f377b64 --- /dev/null +++ b/base/src/commonMain/kotlin/base/event/KeyStrokeSpec.kt @@ -0,0 +1,58 @@ +package jetbrains.datalore.base.event + +class KeyStrokeSpec { + + private val myKeyStrokes: Array + + val keyStrokes: Iterable + get() = listOf(*myKeyStrokes) + + val isEmpty: Boolean + get() = myKeyStrokes.isEmpty() + + constructor(key: Key, vararg modifiers: ModifierKey) { + myKeyStrokes = arrayOf(KeyStroke(key, *modifiers)) + } + + constructor(keyStrokes: Collection) { + myKeyStrokes = keyStrokes.toTypedArray() + } + + constructor(vararg keyStrokes: KeyStroke) { + myKeyStrokes = arrayOf(*keyStrokes) + } + + fun matches(keyStroke: KeyStroke): Boolean { + for (spec in myKeyStrokes) { + if (spec.matches(keyStroke)) { + return true + } + } + return false + } + + fun with(key: ModifierKey): KeyStrokeSpec { + val modified = ArrayList() + for (keyStroke in myKeyStrokes) { + modified.add(keyStroke.with(key)) + } + return KeyStrokeSpec(modified) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + val that = other as KeyStrokeSpec? + return keyStrokes == that!!.keyStrokes + } + + override fun hashCode(): Int { + return keyStrokes.hashCode() + } + + override fun toString(): String { + return keyStrokes.toString() + } + +} \ No newline at end of file diff --git a/base/src/commonMain/kotlin/base/event/KeyStrokeSpecs.kt b/base/src/commonMain/kotlin/base/event/KeyStrokeSpecs.kt new file mode 100644 index 00000000000..9c42150b960 --- /dev/null +++ b/base/src/commonMain/kotlin/base/event/KeyStrokeSpecs.kt @@ -0,0 +1,110 @@ +package jetbrains.datalore.base.event + +import jetbrains.datalore.base.event.ModifierKey.* + +object KeyStrokeSpecs { + val COPY = composite(ctrlOrMeta(Key.C), KeyStrokeSpec(Key.INSERT, CONTROL)) + val CUT = composite(ctrlOrMeta(Key.X), KeyStrokeSpec(Key.DELETE, SHIFT)) + val PASTE = composite(ctrlOrMeta(Key.V), KeyStrokeSpec(Key.INSERT, SHIFT)) + + val UNDO = ctrlOrMeta(Key.Z) + val REDO = UNDO.with(SHIFT) + + val COMPLETE = KeyStrokeSpec(Key.SPACE, CONTROL) + + val SHOW_DOC = composite(KeyStrokeSpec(Key.F1), ctrlOrMeta(Key.J)) + + val HELP = composite(ctrlOrMeta(Key.I), ctrlOrMeta(Key.F1)) + + val HOME = composite(KeyStroke(Key.HOME), KeyStroke(Key.LEFT, META)) + val END = composite(KeyStroke(Key.END), KeyStroke(Key.RIGHT, META)) + + val FILE_HOME = ctrlOrMeta(Key.HOME) + val FILE_END = ctrlOrMeta(Key.END) + + val PREV_WORD = ctrlOrAlt(Key.LEFT) + val NEXT_WORD = ctrlOrAlt(Key.RIGHT) + + val NEXT_EDITABLE = ctrlOrMeta(Key.RIGHT, ALT) + val PREV_EDITABLE = ctrlOrMeta(Key.LEFT, ALT) + + val SELECT_ALL = ctrlOrMeta(Key.A) + + val SELECT_FILE_HOME = FILE_HOME.with(SHIFT) + val SELECT_FILE_END = FILE_END.with(SHIFT) + + val SELECT_HOME = HOME.with(SHIFT) + val SELECT_END = END.with(SHIFT) + + val SELECT_WORD_FORWARD = NEXT_WORD.with(SHIFT) + val SELECT_WORD_BACKWARD = PREV_WORD.with(SHIFT) + + val SELECT_LEFT = KeyStrokeSpec(Key.LEFT, SHIFT) + val SELECT_RIGHT = KeyStrokeSpec(Key.RIGHT, SHIFT) + + val SELECT_UP = KeyStrokeSpec(Key.UP, SHIFT) + val SELECT_DOWN = KeyStrokeSpec(Key.DOWN, SHIFT) + + val INCREASE_SELECTION = KeyStrokeSpec(Key.UP, ALT) + val DECREASE_SELECTION = KeyStrokeSpec(Key.DOWN, ALT) + + val INSERT_BEFORE = composite( + KeyStroke(Key.ENTER, add(META)), + KeyStroke(Key.INSERT), + KeyStroke(Key.ENTER, add(CONTROL)) + ) + val INSERT_AFTER = KeyStrokeSpec(Key.ENTER) + val INSERT = composite(INSERT_BEFORE, INSERT_AFTER) + + val DUPLICATE = ctrlOrMeta(Key.D) + + val DELETE_CURRENT = composite(ctrlOrMeta(Key.BACKSPACE), ctrlOrMeta(Key.DELETE)) + + val DELETE_TO_WORD_START = KeyStrokeSpec(Key.BACKSPACE, ALT) + + val MATCHING_CONSTRUCTS = composite(ctrlOrMeta(Key.LEFT_BRACE, ALT), ctrlOrMeta(Key.RIGHT_BRACE, ALT)) + + val NAVIGATE = ctrlOrMeta(Key.B) + val NAVIGATE_BACK = ctrlOrMeta(Key.LEFT_BRACE) + val NAVIGATE_FORWARD = ctrlOrMeta(Key.RIGHT_BRACE) + + fun ctrlOrMeta(key: Key, vararg modifiers: ModifierKey): KeyStrokeSpec { + return composite(KeyStroke(key, add(CONTROL, *modifiers)), KeyStroke(key, add(META, *modifiers))) + } + + fun ctrlOrAlt(key: Key, vararg modifiers: ModifierKey): KeyStrokeSpec { + return composite(KeyStroke(key, add(CONTROL, *modifiers)), KeyStroke(key, add(ALT, *modifiers))) + } + + private fun add(key: ModifierKey, vararg otherKeys: ModifierKey): Set { + val result = HashSet(otherKeys.asList()) + result.add(key) + return result + } + + fun composite(vararg specs: KeyStrokeSpec): KeyStrokeSpec { + val keyStrokes = HashSet() + for (spec in specs) { + for (ks in spec.keyStrokes) { + keyStrokes.add(ks) + } + } + return KeyStrokeSpec(keyStrokes) + } + + fun composite(vararg specs: KeyStroke): KeyStrokeSpec { + return KeyStrokeSpec(*specs) + } + + fun withoutShift(spec: KeyStrokeSpec): KeyEvent { + val keyStroke = spec.keyStrokes.iterator().next() + val modifiers = keyStroke.modifiers + val withoutShift = HashSet() + for (modifier in modifiers) { + if (modifier !== SHIFT) { + withoutShift.add(modifier) + } + } + return KeyEvent(keyStroke.key, 0.toChar(), withoutShift) + } +} diff --git a/base/src/commonMain/kotlin/base/event/ModifierKey.kt b/base/src/commonMain/kotlin/base/event/ModifierKey.kt new file mode 100644 index 00000000000..309e3d491f8 --- /dev/null +++ b/base/src/commonMain/kotlin/base/event/ModifierKey.kt @@ -0,0 +1,9 @@ +package jetbrains.datalore.base.event + +enum class ModifierKey private constructor() { + + CONTROL, + ALT, + SHIFT, + META +} diff --git a/base/src/jsMain/kotlin/base/event/dom/DomEventUtil.kt b/base/src/jsMain/kotlin/base/event/dom/DomEventUtil.kt new file mode 100644 index 00000000000..6994767be81 --- /dev/null +++ b/base/src/jsMain/kotlin/base/event/dom/DomEventUtil.kt @@ -0,0 +1,93 @@ +package jetbrains.datalore.base.event.dom + +import jetbrains.datalore.base.event.* +import jetbrains.datalore.base.js.dom.DomKeyEvent +import jetbrains.datalore.base.js.dom.DomMouseButtons +import jetbrains.datalore.base.js.dom.DomMouseEvent + +object DomEventUtil { + private fun toKeyEvent(e: DomKeyEvent): KeyEvent { + val key = DomKeyCodeMapper.getKey(e.keyCode) + + val modifiers = HashSet() + if (e.ctrlKey) { + modifiers.add(ModifierKey.CONTROL) + } + if (e.altKey) { + modifiers.add(ModifierKey.ALT) + } + if (e.shiftKey) { + modifiers.add(ModifierKey.SHIFT) + } + if (e.metaKey) { + modifiers.add(ModifierKey.META) + } + return KeyEvent(key, e.charCode.toChar(), modifiers) + } + + fun matches(e: DomKeyEvent, keyStrokeSpec: KeyStrokeSpec): Boolean { + return keyStrokeSpec.matches(toKeyEvent(e).keyStroke) + } + + fun dispatchKeyPress(e: DomKeyEvent, handler: (KeyEvent) -> Unit): Boolean { + val event = toKeyEvent(e) + handler(event) + + //disable back button + if (event.key === Key.BACKSPACE) return false + + //disable navigation keys to prevent browser scrolling + if (event.key === Key.UP || event.key === Key.DOWN || event.key === Key.LEFT || event.key === Key.RIGHT) return false + + //disable space scrolling in case of unhandled space + if (event.key === Key.SPACE) return false + + //disable shift+arrow selection + if (event.`is`(KeyStrokeSpecs.SELECT_LEFT) || event.`is`(KeyStrokeSpecs.SELECT_UP) + || event.`is`(KeyStrokeSpecs.SELECT_RIGHT) || event.`is`(KeyStrokeSpecs.SELECT_DOWN)) { + return false + } + + //disable back forward with Ctrl/Cmd + [ / ] + if (event.`is`(KeyStrokeSpecs.MATCHING_CONSTRUCTS)) return false + + //disable tab navigation + return if (event.`is`(Key.TAB) || event.`is`(Key.TAB, ModifierKey.SHIFT)) false else !event.isConsumed + + } + + internal fun dispatchKeyRelease(e: DomKeyEvent, handler: (KeyEvent) -> Unit): Boolean { + val event = toKeyEvent(e) + handler(event) + return !event.isConsumed + } + + internal fun dispatchKeyType(e: DomKeyEvent, handler: (KeyEvent) -> Unit): Boolean { + val event = toKeyEvent(e) + if (e.charCode < 0x20 || e.charCode == 0x7F) return true + if (e.charCode.toChar() == '\n') return true + if (e.charCode.toChar() == '\r') return true + if (event.has(ModifierKey.META) || event.has(ModifierKey.CONTROL) && !event.has(ModifierKey.ALT)) return true + + handler(event) + + return !event.isConsumed + } + + fun getButton(e: DomMouseEvent): Button { + return when (e.button.toInt()) { + DomMouseButtons.BUTTON_LEFT -> Button.LEFT + DomMouseButtons.BUTTON_MIDDLE -> Button.MIDDLE + DomMouseButtons.BUTTON_RIGHT -> Button.RIGHT + else -> Button.NONE + } + } + + fun getModifiers(e: DomMouseEvent): KeyModifiers { + val ctrlKey = e.ctrlKey + val altKey = e.altKey + val shiftKey = e.shiftKey + val metaKey = e.metaKey + return KeyModifiers(ctrlKey, altKey, shiftKey, metaKey) + } +} diff --git a/base/src/jsMain/kotlin/base/event/dom/DomKeyCodeMapper.kt b/base/src/jsMain/kotlin/base/event/dom/DomKeyCodeMapper.kt new file mode 100644 index 00000000000..5e491936ac1 --- /dev/null +++ b/base/src/jsMain/kotlin/base/event/dom/DomKeyCodeMapper.kt @@ -0,0 +1,98 @@ +package jetbrains.datalore.base.event.dom + +import jetbrains.datalore.base.event.Key + +internal object DomKeyCodeMapper { + + private val KEY_ARRAY = arrayOfNulls(200) + + fun getKey(code: Int): Key { + if (code < KEY_ARRAY.size) { + val result = KEY_ARRAY[code] + if (result != null) { + return result + } + } + return Key.UNKNOWN + } + + init { + KEY_ARRAY[KeyCodes.KEY_ALT] = Key.ALT + KEY_ARRAY[KeyCodes.KEY_BACKSPACE] = Key.BACKSPACE + KEY_ARRAY[KeyCodes.KEY_CTRL] = Key.CONTROL + KEY_ARRAY[KeyCodes.KEY_DELETE] = Key.DELETE + KEY_ARRAY[KeyCodes.KEY_DOWN] = Key.DOWN + KEY_ARRAY[KeyCodes.KEY_END] = Key.END + KEY_ARRAY[KeyCodes.KEY_ENTER] = Key.ENTER + KEY_ARRAY[KeyCodes.KEY_ESCAPE] = Key.ESCAPE + KEY_ARRAY[KeyCodes.KEY_HOME] = Key.HOME + KEY_ARRAY[KeyCodes.KEY_LEFT] = Key.LEFT + KEY_ARRAY[KeyCodes.KEY_PAGEDOWN] = Key.PAGE_DOWN + KEY_ARRAY[KeyCodes.KEY_PAGEUP] = Key.PAGE_UP + KEY_ARRAY[KeyCodes.KEY_RIGHT] = Key.RIGHT + KEY_ARRAY[KeyCodes.KEY_SHIFT] = Key.SHIFT + KEY_ARRAY[KeyCodes.KEY_TAB] = Key.TAB + KEY_ARRAY[KeyCodes.KEY_UP] = Key.UP + KEY_ARRAY[45] = Key.INSERT + KEY_ARRAY[32] = Key.SPACE + + KEY_ARRAY['A'.toInt()] = Key.A + KEY_ARRAY['B'.toInt()] = Key.B + KEY_ARRAY['C'.toInt()] = Key.C + KEY_ARRAY['D'.toInt()] = Key.D + KEY_ARRAY['E'.toInt()] = Key.E + KEY_ARRAY['F'.toInt()] = Key.F + KEY_ARRAY['G'.toInt()] = Key.G + KEY_ARRAY['H'.toInt()] = Key.H + KEY_ARRAY['I'.toInt()] = Key.I + KEY_ARRAY['J'.toInt()] = Key.J + KEY_ARRAY['K'.toInt()] = Key.K + KEY_ARRAY['L'.toInt()] = Key.L + KEY_ARRAY['M'.toInt()] = Key.M + KEY_ARRAY['N'.toInt()] = Key.N + KEY_ARRAY['O'.toInt()] = Key.O + KEY_ARRAY['P'.toInt()] = Key.P + KEY_ARRAY['Q'.toInt()] = Key.Q + KEY_ARRAY['R'.toInt()] = Key.R + KEY_ARRAY['S'.toInt()] = Key.S + KEY_ARRAY['T'.toInt()] = Key.T + KEY_ARRAY['U'.toInt()] = Key.U + KEY_ARRAY['V'.toInt()] = Key.V + KEY_ARRAY['W'.toInt()] = Key.W + KEY_ARRAY['X'.toInt()] = Key.X + KEY_ARRAY['Y'.toInt()] = Key.Y + KEY_ARRAY['Z'.toInt()] = Key.Z + + KEY_ARRAY[219] = Key.LEFT_BRACE + KEY_ARRAY[221] = Key.RIGHT_BRACE + + KEY_ARRAY['0'.toInt()] = Key.DIGIT_0 + KEY_ARRAY['1'.toInt()] = Key.DIGIT_1 + KEY_ARRAY['2'.toInt()] = Key.DIGIT_2 + KEY_ARRAY['3'.toInt()] = Key.DIGIT_3 + KEY_ARRAY['4'.toInt()] = Key.DIGIT_4 + KEY_ARRAY['5'.toInt()] = Key.DIGIT_5 + KEY_ARRAY['6'.toInt()] = Key.DIGIT_6 + KEY_ARRAY['7'.toInt()] = Key.DIGIT_7 + KEY_ARRAY['8'.toInt()] = Key.DIGIT_8 + KEY_ARRAY['9'.toInt()] = Key.DIGIT_9 + + KEY_ARRAY[112] = Key.F1 + KEY_ARRAY[113] = Key.F2 + KEY_ARRAY[114] = Key.F3 + KEY_ARRAY[115] = Key.F4 + KEY_ARRAY[116] = Key.F5 + KEY_ARRAY[117] = Key.F6 + KEY_ARRAY[118] = Key.F7 + KEY_ARRAY[119] = Key.F8 + KEY_ARRAY[120] = Key.F9 + KEY_ARRAY[121] = Key.F10 + KEY_ARRAY[122] = Key.F11 + KEY_ARRAY[123] = Key.F12 + + KEY_ARRAY[188] = Key.COMMA + KEY_ARRAY[190] = Key.PERIOD + KEY_ARRAY[191] = Key.SLASH + KEY_ARRAY[192] = Key.BACK_QUOTE + } +} diff --git a/base/src/jsMain/kotlin/base/event/dom/KeyCodes.kt b/base/src/jsMain/kotlin/base/event/dom/KeyCodes.kt new file mode 100644 index 00000000000..a3e96d0f9f8 --- /dev/null +++ b/base/src/jsMain/kotlin/base/event/dom/KeyCodes.kt @@ -0,0 +1,98 @@ +package jetbrains.datalore.base.event.dom + +object KeyCodes { + + val KEY_A = 65 + val KEY_B = 66 + val KEY_C = 67 + val KEY_D = 68 + val KEY_E = 69 + val KEY_F = 70 + val KEY_G = 71 + val KEY_H = 72 + val KEY_I = 73 + val KEY_J = 74 + val KEY_K = 75 + val KEY_L = 76 + val KEY_M = 77 + val KEY_N = 78 + val KEY_O = 79 + val KEY_P = 80 + val KEY_Q = 81 + val KEY_R = 82 + val KEY_S = 83 + val KEY_T = 84 + val KEY_U = 85 + val KEY_V = 86 + val KEY_W = 87 + val KEY_X = 88 + val KEY_Y = 89 + val KEY_Z = 90 + + val KEY_ZERO = 48 + val KEY_ONE = 49 + val KEY_TWO = 50 + val KEY_THREE = 51 + val KEY_FOUR = 52 + val KEY_FIVE = 53 + val KEY_SIX = 54 + val KEY_SEVEN = 55 + val KEY_EIGHT = 56 + val KEY_NINE = 57 + + val KEY_NUM_ZERO = 96 + val KEY_NUM_ONE = 97 + val KEY_NUM_TWO = 98 + val KEY_NUM_THREE = 99 + val KEY_NUM_FOUR = 100 + val KEY_NUM_FIVE = 101 + val KEY_NUM_SIX = 102 + val KEY_NUM_SEVEN = 103 + val KEY_NUM_EIGHT = 104 + val KEY_NUM_NINE = 105 + val KEY_NUM_MULTIPLY = 106 + val KEY_NUM_PLUS = 107 + val KEY_NUM_MINUS = 109 + val KEY_NUM_PERIOD = 110 + val KEY_NUM_DIVISION = 111 + val KEY_ALT = 18 + + val KEY_BACKSPACE = 8 + val KEY_CTRL = 17 + + val KEY_DELETE = 46 + + val KEY_UP = 38 + val KEY_DOWN = 40 + + val KEY_END = 35 + + val KEY_ENTER = 13 + val KEY_ESCAPE = 27 + val KEY_HOME = 36 + val KEY_LEFT = 37 + val KEY_PAGEDOWN = 34 + val KEY_PAGEUP = 33 + val KEY_RIGHT = 39 + val KEY_SHIFT = 16 + + val KEY_TAB = 9 + + val KEY_F1 = 112 + val KEY_F2 = 113 + val KEY_F3 = 114 + val KEY_F4 = 115 + val KEY_F5 = 116 + val KEY_F6 = 117 + val KEY_F7 = 118 + val KEY_F8 = 119 + val KEY_F9 = 120 + val KEY_F10 = 121 + val KEY_F11 = 122 + val KEY_F12 = 123 + val KEY_WIN_KEY_FF_LINUX = 0 + val KEY_MAC_ENTER = 3 + val KEY_PAUSE = 19 + val KEY_CAPS_LOCK = 20 + val KEY_SPACE = 32 +} diff --git a/base/src/jsMain/kotlin/base/js/css/CssUnit.kt b/base/src/jsMain/kotlin/base/js/css/CssUnit.kt new file mode 100644 index 00000000000..c9996ecab0f --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/css/CssUnit.kt @@ -0,0 +1,12 @@ +package jetbrains.datalore.base.js.css + +enum class CssUnit private constructor(val stringRepresentation: String) { + + EM("em"), + NUMBER(""), + ENUMERABLE(""), + PX("px"), + PERCENT("%"), + VW("vw"), + VH("vh") +} diff --git a/base/src/jsMain/kotlin/base/js/css/CssUnitQualifier.kt b/base/src/jsMain/kotlin/base/js/css/CssUnitQualifier.kt new file mode 100644 index 00000000000..e2433496a16 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/css/CssUnitQualifier.kt @@ -0,0 +1,3 @@ +package jetbrains.datalore.base.js.css + +interface CssUnitQualifier : HasStringQualifier diff --git a/base/src/jsMain/kotlin/base/js/css/HasStringQualifier.kt b/base/src/jsMain/kotlin/base/js/css/HasStringQualifier.kt new file mode 100644 index 00000000000..a7cf2c02bf0 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/css/HasStringQualifier.kt @@ -0,0 +1,5 @@ +package jetbrains.datalore.base.js.css + +interface HasStringQualifier { + val stringQualifier: String +} diff --git a/base/src/jsMain/kotlin/base/js/css/StyleMap.kt b/base/src/jsMain/kotlin/base/js/css/StyleMap.kt new file mode 100644 index 00000000000..8b8f1ef7b51 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/css/StyleMap.kt @@ -0,0 +1,469 @@ +package jetbrains.datalore.base.js.css + +import jetbrains.datalore.base.js.css.enumerables.* +import org.w3c.dom.css.CSSStyleDeclaration + +typealias StyleMap = CSSStyleDeclaration + +val StyleMap.cssPosition: CssPosition? + get() = CssPosition.parse(getProperty("position")) + +val StyleMap.cssOverflow: CssOverflow? + get() = CssOverflow.parse(getProperty("overflow")) + +val StyleMap.cssOverflowX: CssOverflow? + get() = CssOverflow.parse(getProperty("overflow-x")) + +val StyleMap.cssOverflowY: CssOverflow? + get() = CssOverflow.parse(getProperty("overflow-y")) + +fun StyleMap.setWidth(width: Int, unit: CssUnit = CssUnit.PX): StyleMap { + return doSetProperty("width", width, unit) +} + +fun StyleMap.clearWidth(): StyleMap { + return clearProperty("width") +} + +fun StyleMap.setMinWidth(width: Double, unit: CssUnit = CssUnit.PX): StyleMap { + return doSetProperty("min-width", width, unit) +} + +fun StyleMap.setMaxWidth(width: Double, unit: CssUnit = CssUnit.PX): StyleMap { + return doSetProperty("max-width", width, unit) +} + +fun StyleMap.setHeight(height: Int, unit: CssUnit = CssUnit.PX): StyleMap { + return doSetProperty("height", height, unit) +} + +fun StyleMap.clearHeight(): StyleMap { + return clearProperty("height") +} + +fun StyleMap.setMinHeight(height: Double, unit: CssUnit = CssUnit.PX): StyleMap { + return doSetProperty("min-height", height, unit) +} + +fun StyleMap.setMaxHeight(height: Double, unit: CssUnit = CssUnit.PX): StyleMap { + return doSetProperty("max-height", height, unit) +} + +fun StyleMap.clearMaxHeight(): StyleMap { + return clearProperty("max-height") +} + +fun StyleMap.setMargin(margin: Double, unit: CssUnit = CssUnit.PX): StyleMap { + setMarginLeft(margin, unit) + setMarginRight(margin, unit) + setMarginTop(margin, unit) + setMarginBottom(margin, unit) + return this +} + +fun StyleMap.setMarginLeft(margin: Double, unit: CssUnit = CssUnit.PX): StyleMap { + return doSetProperty("margin-left", margin, unit) +} + +fun StyleMap.setMarginRight(margin: Double, unit: CssUnit = CssUnit.PX): StyleMap { + return doSetProperty("margin-right", margin, unit) +} + +fun StyleMap.setMarginTop(margin: Double, unit: CssUnit = CssUnit.PX): StyleMap { + return doSetProperty("margin-top", margin, unit) +} + +fun StyleMap.clearMarginTop(): StyleMap { + return clearProperty("margin-top") +} + +fun StyleMap.setMarginBottom(margin: Double, unit: CssUnit = CssUnit.PX): StyleMap { + return doSetProperty("margin-bottom", margin, unit) +} + +fun StyleMap.clearMarginBottom(): StyleMap { + return clearProperty("margin-bottom") +} + +fun StyleMap.setPadding(padding: Double, unit: CssUnit = CssUnit.PX): StyleMap { + setPaddingLeft(padding, unit) + setPaddingRight(padding, unit) + setPaddingTop(padding, unit) + setPaddingBottom(padding, unit) + return this +} + +fun StyleMap.setPaddingLeft(padding: Double, unit: CssUnit = CssUnit.PX): StyleMap { + return doSetProperty("padding-left", padding, unit) +} + +fun StyleMap.setPaddingRight(padding: Double, unit: CssUnit = CssUnit.PX): StyleMap { + return doSetProperty("padding-right", padding, unit) +} + +fun StyleMap.setPaddingTop(padding: Double, unit: CssUnit = CssUnit.PX): StyleMap { + return doSetProperty("padding-top", padding, unit) +} + +fun StyleMap.setPaddingBottom(padding: Double, unit: CssUnit = CssUnit.PX): StyleMap { + return doSetProperty("padding-bottom", padding, unit) +} + +fun StyleMap.setBorder(border: String): StyleMap { + return doSetProperty("border", border) +} + +fun StyleMap.setBorderStyle(value: CssBorderStyle): StyleMap { + return doSetProperty("border-style", value) +} + +fun StyleMap.clearBorder(): StyleMap { + return clearProperty("border") +} + +fun StyleMap.clearBorderWidth(): StyleMap { + return clearProperty("border-width") +} + +fun StyleMap.setBorderLeft(border: String): StyleMap { + return doSetProperty("border-left", border) +} + +fun StyleMap.clearBorderLeft(): StyleMap { + return clearProperty("border-left") +} + +fun StyleMap.setBorderRight(border: String): StyleMap { + return doSetProperty("border-right", border) +} + +fun StyleMap.clearBorderRight(): StyleMap { + return clearProperty("border-right") +} + +fun StyleMap.setBorderTop(border: String): StyleMap { + return doSetProperty("border-top", border) +} + +fun StyleMap.clearBorderTop(): StyleMap { + return clearProperty("border-top") +} + +fun StyleMap.setBorderBottom(border: String): StyleMap { + return doSetProperty("border-bottom", border) +} + +fun StyleMap.clearBorderBottom(): StyleMap { + return clearProperty("border-bottom") +} + +fun StyleMap.setBorderColor(color: String): StyleMap { + return doSetProperty("border-color", color) +} + +fun StyleMap.setBorderLeftColor(color: String): StyleMap { + return doSetProperty("border-left-color", color) +} + +fun StyleMap.setBorderRightColor(color: String): StyleMap { + return doSetProperty("border-right-color", color) +} + +fun StyleMap.setBorderTopColor(color: String): StyleMap { + return doSetProperty("border-top-color", color) +} + +fun StyleMap.setBorderBottomColor(color: String): StyleMap { + return doSetProperty("border-bottom-color", color) +} + +fun StyleMap.clearBorderLeftColor(): StyleMap { + return clearProperty("border-left-color") +} + +fun StyleMap.clearBorderRightColor(): StyleMap { + return clearProperty("border-right-color") +} + +fun StyleMap.clearBorderTopColor(): StyleMap { + return clearProperty("border-top-color") +} + +fun StyleMap.clearBorderBottomColor(): StyleMap { + return clearProperty("border-bottom-color") +} + +fun StyleMap.setTop(top: Double, unit: CssUnit = CssUnit.PX): StyleMap { + return doSetProperty("top", top, unit) +} + +fun StyleMap.clearTop(): StyleMap { + return clearProperty("top") +} + +fun StyleMap.setBottom(bottom: Double, unit: CssUnit = CssUnit.PX): StyleMap { + return doSetProperty("bottom", bottom, unit) +} + +fun StyleMap.clearBottom(): StyleMap { + return clearProperty("bottom") +} + +fun StyleMap.setRight(right: Double, unit: CssUnit = CssUnit.PX): StyleMap { + return doSetProperty("right", right, unit) +} + +fun StyleMap.clearRight(): StyleMap { + return clearProperty("right") +} + +fun StyleMap.setLeft(left: Double, unit: CssUnit = CssUnit.PX): StyleMap { + return doSetProperty("left", left, unit) +} + +fun StyleMap.clearLeft(): StyleMap { + return clearProperty("left") +} + +fun StyleMap.setOpacity(opacity: Double): StyleMap { + return doSetProperty("opacity", opacity.toString()) +} + +fun StyleMap.clearOpacity(): StyleMap { + return clearProperty("opacity") +} + +fun StyleMap.setZIndex(zIndex: Int): StyleMap { + return doSetProperty("z-index", zIndex.toString()) +} + +fun StyleMap.setDisplay(display: CssDisplay): StyleMap { + return doSetProperty("display", display) +} + +fun StyleMap.clearDisplay(): StyleMap { + return clearProperty("display") +} + +fun StyleMap.setPosition(position: CssPosition): StyleMap { + return doSetProperty("position", position) +} + +fun StyleMap.setVisibility(visibility: CssVisibility): StyleMap { + return doSetProperty("visibility", visibility) +} + +fun StyleMap.setBackgroundColor(color: String): StyleMap { + return doSetProperty("background-color", color) +} + +fun StyleMap.clearBackgroundColor(): StyleMap { + return clearProperty("background-color") +} + +fun StyleMap.setBackgroundImage(image: String): StyleMap { + return doSetProperty("background-image", image) +} + +fun StyleMap.setBackgroundSize(size: String): StyleMap { + return doSetProperty("background-size", size) +} + +fun StyleMap.setColor(color: String): StyleMap { + return doSetProperty("color", color) +} + +fun StyleMap.setFont(value: String): StyleMap { + return doSetProperty("font", value) +} + +fun StyleMap.setFontFamily(value: String): StyleMap { + return doSetProperty("font-family", value) +} + +fun StyleMap.setFontStyle(value: CssFontStyle): StyleMap { + return doSetProperty("font-style", value) +} + +fun StyleMap.setFontWeight(value: CssFontWeight): StyleMap { + return doSetProperty("font-weight", value) +} + +fun StyleMap.setFontSize(value: Double, unit: CssUnit = CssUnit.PX): StyleMap { + return doSetProperty("font-size", value, unit) +} + +fun StyleMap.setTextDecoration(value: String): StyleMap { + return doSetProperty("text-decoration", value) +} + +fun StyleMap.clearTextDecoration(): StyleMap { + return clearProperty("text-decoration") +} + +fun StyleMap.setTextDecorationColor(value: String): StyleMap { + return doSetProperty("text-decoration-color", value) +} + +fun StyleMap.setUserSelect(value: String): StyleMap { + return doSetProperty("user-select", value) +} + +fun StyleMap.setTransform(transform: String): StyleMap { + return doSetProperty("transform", transform) +} + +fun StyleMap.clearTransform(): StyleMap { + return clearProperty("transform") +} + +fun StyleMap.setTransition(transition: String): StyleMap { + return doSetProperty("transition", transition) +} + +fun StyleMap.clearTransition(): StyleMap { + return clearProperty("transition") +} + +fun StyleMap.setCursor(cursor: CssCursor): StyleMap { + return doSetProperty("cursor", cursor) +} + +fun StyleMap.setPointerEvents(pointerEvents: CssPointerEvents): StyleMap { + return doSetProperty("pointer-events", pointerEvents) +} + +fun StyleMap.clearPointerEvents(): StyleMap { + return clearProperty("pointer-events") +} + +fun StyleMap.setGridTemplateColumns(template: String): StyleMap { + return doSetProperty("grid-template-columns", template) +} + +fun StyleMap.setGridRow(gridRow: String): StyleMap { + return doSetProperty("grid-row", gridRow) +} + +fun StyleMap.setGridColumn(gridColumn: String): StyleMap { + return doSetProperty("grid-column", gridColumn) +} + +fun StyleMap.setAlignItems(alignItems: CssAlignItem): StyleMap { + return doSetProperty("align-items", alignItems) +} + +fun StyleMap.clearAlignItems(): StyleMap { + return clearProperty("align-items") +} + +fun StyleMap.setSelfAlignment(alignItems: CssAlignItem): StyleMap { + return doSetProperty("self-alignment", alignItems) +} + +fun StyleMap.clearSelfAlignment(): StyleMap { + return clearProperty("self-alignment") +} + +fun StyleMap.setOutline(outline: String): StyleMap { + return doSetProperty("outline", outline) +} + +fun StyleMap.clearOutline(): StyleMap { + return clearProperty("outline") +} + +fun StyleMap.setOutlineColor(color: String): StyleMap { + return doSetProperty("outline-color", color) +} + +fun StyleMap.clearOutlineColor(): StyleMap { + return clearProperty("outline-color") +} + +fun StyleMap.setOutlineWidth(width: Double, unit: CssUnit = CssUnit.PX): StyleMap { + return doSetProperty("outline-width", width, unit) +} + +fun StyleMap.setOutlineStyle(value: CssOutlineStyle): StyleMap { + return doSetProperty("outline-style", value) +} + +fun StyleMap.clearOutlineStyle(): StyleMap { + return clearProperty("outline-style") +} + +fun StyleMap.setFill(fill: String): StyleMap { + return doSetProperty("fill", fill) +} + +fun StyleMap.setStroke(stroke: String): StyleMap { + return doSetProperty("stroke", stroke) +} + +fun StyleMap.setStrokeWidth(width: Double, unit: CssUnit = CssUnit.PX): StyleMap { + return doSetProperty("stroke-width", width, unit) +} + +fun StyleMap.setWhiteSpace(whiteSpace: CssWhiteSpace): StyleMap { + return doSetProperty("white-space", whiteSpace) +} + +fun StyleMap.setVerticalAlign(align: CssVerticalAlign): StyleMap { + return doSetProperty("vertical-align", align) +} + +fun StyleMap.setBoxShadow(value: String): StyleMap { + return doSetProperty("box-shadow", value) +} + +fun StyleMap.clearBoxShadow(): StyleMap { + return clearProperty("box-shadow") +} + +fun StyleMap.setOverflow(overflow: CssOverflow): StyleMap { + return doSetProperty("overflow", overflow) +} + +fun StyleMap.setOverflowX(overflow: CssOverflow): StyleMap { + return doSetProperty("overflow-x", overflow) +} + +fun StyleMap.setOverflowY(overflow: CssOverflow): StyleMap { + return doSetProperty("overflow-y", overflow) +} + +fun StyleMap.setClear(clear: CssClear): StyleMap { + return doSetProperty("clear", clear) +} + +fun StyleMap.setFloat(cssFloat: CssFloat): StyleMap { + return doSetProperty("float", cssFloat) +} + +fun StyleMap.setCssVariable(name: String, value: String): StyleMap { + return doSetProperty("--$name", value) +} + +private fun StyleMap.doSetProperty(name: String, value: String): StyleMap { + setProperty(name, value) + return this +} + +private fun StyleMap.doSetProperty(name: String, value: ValueT, unit: CssUnit): StyleMap { + return doSetProperty(name, value.toString() + unit.stringRepresentation) +} + +private fun StyleMap.doSetProperty(name: String, value: CssUnitQualifier): StyleMap { + doSetProperty(name, value.stringQualifier) + return this +} + +fun StyleMap.getProperty(name: String): String { + return getPropertyValue(name) +} + +private fun StyleMap.clearProperty(name: String): StyleMap { + removeProperty(name) + return this +} diff --git a/base/src/jsMain/kotlin/base/js/css/enumerables/CssAlignItem.kt b/base/src/jsMain/kotlin/base/js/css/enumerables/CssAlignItem.kt new file mode 100644 index 00000000000..81d65272974 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/css/enumerables/CssAlignItem.kt @@ -0,0 +1,9 @@ +package jetbrains.datalore.base.js.css.enumerables + +enum class CssAlignItem constructor(override val stringQualifier: String) : CssBaseValue { + DEFAULT("default"), + CENTER("center"), + STRETCH("stretch"), + FLEX_START("flex-start"), + FLEX_END("flex-end"); +} diff --git a/base/src/jsMain/kotlin/base/js/css/enumerables/CssBaseValue.kt b/base/src/jsMain/kotlin/base/js/css/enumerables/CssBaseValue.kt new file mode 100644 index 00000000000..d9f89bf90b5 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/css/enumerables/CssBaseValue.kt @@ -0,0 +1,14 @@ +package jetbrains.datalore.base.js.css.enumerables + +import jetbrains.datalore.base.js.css.CssUnitQualifier + +interface CssBaseValue : CssUnitQualifier + +internal fun parse(str: String, values: Array): TypeT? { + for (value in values) { + if (value.stringQualifier.equals(str, ignoreCase = true)) { + return value + } + } + return null +} diff --git a/base/src/jsMain/kotlin/base/js/css/enumerables/CssBorderStyle.kt b/base/src/jsMain/kotlin/base/js/css/enumerables/CssBorderStyle.kt new file mode 100644 index 00000000000..3b5d8062d52 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/css/enumerables/CssBorderStyle.kt @@ -0,0 +1,9 @@ +package jetbrains.datalore.base.js.css.enumerables + +enum class CssBorderStyle constructor(override val stringQualifier: String) : CssBaseValue { + NONE("none"), + DOTTED("dotted"), + DASHED("dashed"), + HIDDEN("hidden"), + SOLID("solid"); +} diff --git a/base/src/jsMain/kotlin/base/js/css/enumerables/CssClear.kt b/base/src/jsMain/kotlin/base/js/css/enumerables/CssClear.kt new file mode 100644 index 00000000000..81b5c4cd24e --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/css/enumerables/CssClear.kt @@ -0,0 +1,8 @@ +package jetbrains.datalore.base.js.css.enumerables + +enum class CssClear constructor(override val stringQualifier: String) : CssBaseValue { + NONE("none"), + BOTH("both"), + LEFT("left"), + RIGHT("right"); +} diff --git a/base/src/jsMain/kotlin/base/js/css/enumerables/CssCursor.kt b/base/src/jsMain/kotlin/base/js/css/enumerables/CssCursor.kt new file mode 100644 index 00000000000..3c7213e98a1 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/css/enumerables/CssCursor.kt @@ -0,0 +1,6 @@ +package jetbrains.datalore.base.js.css.enumerables + +enum class CssCursor constructor(override val stringQualifier: String) : CssBaseValue { + DEFAULT("default"), + POINTER("pointer"); +} diff --git a/base/src/jsMain/kotlin/base/js/css/enumerables/CssDisplay.kt b/base/src/jsMain/kotlin/base/js/css/enumerables/CssDisplay.kt new file mode 100644 index 00000000000..2b38eda7fc6 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/css/enumerables/CssDisplay.kt @@ -0,0 +1,10 @@ +package jetbrains.datalore.base.js.css.enumerables + +enum class CssDisplay constructor(override val stringQualifier: String) : CssBaseValue { + DEFAULT("default"), + NONE("none"), + BLOCK("block"), + FLEX("flex"), + GRID("grid"), + INLINE_BLOCK("inline-block"); +} diff --git a/base/src/jsMain/kotlin/base/js/css/enumerables/CssFloat.kt b/base/src/jsMain/kotlin/base/js/css/enumerables/CssFloat.kt new file mode 100644 index 00000000000..508b9371e07 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/css/enumerables/CssFloat.kt @@ -0,0 +1,7 @@ +package jetbrains.datalore.base.js.css.enumerables + +enum class CssFloat constructor(override val stringQualifier: String) : CssBaseValue { + NONE("none"), + LEFT("left"), + RIGHT("right"); +} diff --git a/base/src/jsMain/kotlin/base/js/css/enumerables/CssFontStyle.kt b/base/src/jsMain/kotlin/base/js/css/enumerables/CssFontStyle.kt new file mode 100644 index 00000000000..9a2f4cffcef --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/css/enumerables/CssFontStyle.kt @@ -0,0 +1,6 @@ +package jetbrains.datalore.base.js.css.enumerables + +enum class CssFontStyle constructor(override val stringQualifier: String) : CssBaseValue { + NORMAL("normal"), + ITALIC("italic"); +} diff --git a/base/src/jsMain/kotlin/base/js/css/enumerables/CssFontWeight.kt b/base/src/jsMain/kotlin/base/js/css/enumerables/CssFontWeight.kt new file mode 100644 index 00000000000..d026c74a63c --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/css/enumerables/CssFontWeight.kt @@ -0,0 +1,8 @@ +package jetbrains.datalore.base.js.css.enumerables + +enum class CssFontWeight constructor(override val stringQualifier: String) : CssBaseValue { + NORMAL("normal"), + BOLD("bold"), + BOLDER("bolder"), + LIGHTER("lighter"); +} diff --git a/base/src/jsMain/kotlin/base/js/css/enumerables/CssLineCap.kt b/base/src/jsMain/kotlin/base/js/css/enumerables/CssLineCap.kt new file mode 100644 index 00000000000..110e0cb4526 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/css/enumerables/CssLineCap.kt @@ -0,0 +1,5 @@ +package jetbrains.datalore.base.js.css.enumerables + +import org.w3c.dom.CanvasLineCap + +typealias CssLineCap = CanvasLineCap diff --git a/base/src/jsMain/kotlin/base/js/css/enumerables/CssLineJoin.kt b/base/src/jsMain/kotlin/base/js/css/enumerables/CssLineJoin.kt new file mode 100644 index 00000000000..e26a5686c7a --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/css/enumerables/CssLineJoin.kt @@ -0,0 +1,5 @@ +package jetbrains.datalore.base.js.css.enumerables + +import org.w3c.dom.CanvasLineJoin + +typealias CssLineJoin = CanvasLineJoin diff --git a/base/src/jsMain/kotlin/base/js/css/enumerables/CssOutlineStyle.kt b/base/src/jsMain/kotlin/base/js/css/enumerables/CssOutlineStyle.kt new file mode 100644 index 00000000000..885706b5640 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/css/enumerables/CssOutlineStyle.kt @@ -0,0 +1,13 @@ +package jetbrains.datalore.base.js.css.enumerables + +enum class CssOutlineStyle constructor(override val stringQualifier: String) : CssBaseValue { + NONE("none"), + DASHED("dashed"), + DOTTED("dotted"), + DOUBLE("double"), + GROOVE("groove"), + INSET("inset"), + OUTSET("outset"), + RIDGE("ridge"), + SOLID("solid"); +} diff --git a/base/src/jsMain/kotlin/base/js/css/enumerables/CssOverflow.kt b/base/src/jsMain/kotlin/base/js/css/enumerables/CssOverflow.kt new file mode 100644 index 00000000000..383d767b7cb --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/css/enumerables/CssOverflow.kt @@ -0,0 +1,16 @@ +package jetbrains.datalore.base.js.css.enumerables + +enum class CssOverflow private constructor(override val stringQualifier: String) : CssBaseValue { + VISIBLE("visible"), + HIDDEN("hidden"), + SCROLL("scroll"), + AUTO("auto"), + INITIAL("initial"), + INHERIT("inherit"); + + companion object { + fun parse(value: String): CssOverflow? { + return parse(value, values()) + } + } +} diff --git a/base/src/jsMain/kotlin/base/js/css/enumerables/CssPointerEvents.kt b/base/src/jsMain/kotlin/base/js/css/enumerables/CssPointerEvents.kt new file mode 100644 index 00000000000..0532993c7e3 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/css/enumerables/CssPointerEvents.kt @@ -0,0 +1,6 @@ +package jetbrains.datalore.base.js.css.enumerables + +enum class CssPointerEvents constructor(override val stringQualifier: String) : CssBaseValue { + NONE("none"), + AUTO("auto"); +} diff --git a/base/src/jsMain/kotlin/base/js/css/enumerables/CssPosition.kt b/base/src/jsMain/kotlin/base/js/css/enumerables/CssPosition.kt new file mode 100644 index 00000000000..3f769447d5f --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/css/enumerables/CssPosition.kt @@ -0,0 +1,15 @@ +package jetbrains.datalore.base.js.css.enumerables + +enum class CssPosition constructor(override val stringQualifier: String) : CssBaseValue { + ABSOLUTE("absolute"), + FIXED("fixed"), + RELATIVE("relative"), + STATIC("static"), + STICKY("sticky"); + + companion object { + fun parse(value: String): CssPosition? { + return parse(value, values()) + } + } +} diff --git a/base/src/jsMain/kotlin/base/js/css/enumerables/CssTextAlign.kt b/base/src/jsMain/kotlin/base/js/css/enumerables/CssTextAlign.kt new file mode 100644 index 00000000000..4ec1a72de0b --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/css/enumerables/CssTextAlign.kt @@ -0,0 +1,5 @@ +package jetbrains.datalore.base.js.css.enumerables + +import org.w3c.dom.CanvasTextAlign + +typealias CssTextAlign = CanvasTextAlign diff --git a/base/src/jsMain/kotlin/base/js/css/enumerables/CssTextBaseLine.kt b/base/src/jsMain/kotlin/base/js/css/enumerables/CssTextBaseLine.kt new file mode 100644 index 00000000000..1d1c5289a9f --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/css/enumerables/CssTextBaseLine.kt @@ -0,0 +1,5 @@ +package jetbrains.datalore.base.js.css.enumerables + +import org.w3c.dom.CanvasTextBaseline + +typealias CssTextBaseLine = CanvasTextBaseline diff --git a/base/src/jsMain/kotlin/base/js/css/enumerables/CssVerticalAlign.kt b/base/src/jsMain/kotlin/base/js/css/enumerables/CssVerticalAlign.kt new file mode 100644 index 00000000000..b7b3a37812e --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/css/enumerables/CssVerticalAlign.kt @@ -0,0 +1,12 @@ +package jetbrains.datalore.base.js.css.enumerables + +enum class CssVerticalAlign constructor(override val stringQualifier: String) : CssBaseValue { + BASELINE("baseline"), + SUB("sub"), + SUPER("super"), + TOP("top"), + TEXT_TOP("text_top"), + MIDDLE("middle"), + BOTTOM("bottom"), + TEXT_BOTTOM("text_bottom"); +} diff --git a/base/src/jsMain/kotlin/base/js/css/enumerables/CssVisibility.kt b/base/src/jsMain/kotlin/base/js/css/enumerables/CssVisibility.kt new file mode 100644 index 00000000000..4578c6ab9f0 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/css/enumerables/CssVisibility.kt @@ -0,0 +1,7 @@ +package jetbrains.datalore.base.js.css.enumerables + +enum class CssVisibility constructor(override val stringQualifier: String) : CssBaseValue { + COLLAPSE("collapse"), + HIDDEN("hidden"), + VISIBLE("visible"); +} diff --git a/base/src/jsMain/kotlin/base/js/css/enumerables/CssWhiteSpace.kt b/base/src/jsMain/kotlin/base/js/css/enumerables/CssWhiteSpace.kt new file mode 100644 index 00000000000..20c47742a48 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/css/enumerables/CssWhiteSpace.kt @@ -0,0 +1,9 @@ +package jetbrains.datalore.base.js.css.enumerables + +enum class CssWhiteSpace constructor(override val stringQualifier: String) : CssBaseValue { + NORMAL("normal"), + NOWRAP("nowrap"), + PRE("pre"), + PRE_LINE("pre-line"), + PRE_WRAP("pre-wrap"); +} diff --git a/base/src/jsMain/kotlin/base/js/dom/DomApi.kt b/base/src/jsMain/kotlin/base/js/dom/DomApi.kt new file mode 100644 index 00000000000..de5f9b5be64 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomApi.kt @@ -0,0 +1,115 @@ +package jetbrains.datalore.base.js.dom + +object DomApi { + + val body: DomHTMLElement? + get() = document.body + + val document: DomDocument + get() = DomWindow.getDocument() + + val active: DomElement? + get() = document.activeElement + + fun createDiv(): DomElement { + return createElement("div") + } + + fun createSpan(): DomElement { + return createElement("span") + } + + fun createList(): DomElement { + return createElement("ul") + } + + fun createLi(): DomElement { + return createElement("li") + } + + fun createAnchor(): DomElement { + return createElement("a") + } + + fun createButton(): DomElement { + return createElement("button") + } + + fun createTR(): DomElement { + return createElement("tr") + } + + fun createTH(): DomElement { + return createElement("th") + } + + fun createTD(): DomElement { + return createElement("td") + } + + fun createHR(): DomElement { + return createElement("hr") + } + + fun createItalic(): DomElement { + return createElement("i") + } + + fun createForm(): DomElement { + return createElement("form") + } + + fun createCanvas(): DomHTMLCanvasElement { + return createElement("canvas").cast() + } + + fun createInput(type: String): InputDomElement { + val input: InputDomElement = createElement("input").cast() + input.setAttribute("type", type) + return input + } + + fun createElement(tag: String): DomElement { + return DomWindow.getDocument().createElement(tag) + } + + fun createInputText(): InputDomElement { + return createInput("text") + } + + fun createCheckbox(): InputDomElement { + return createInput("checkbox") + } + + fun createRadio(): InputDomElement { + return createInput("radio").cast() + } + + fun createTextArea(): TextAreaDomElement { + return createElement("textarea").cast() + } + + fun createSelect(): DomElement { + return createElement("select") + } + + fun createOption(): OptionDomElement { + return createElement("option").cast() + } + + fun createImage(): ImageDomElement { + return createElement("img").cast() + } + + fun createBR(): DomElement { + return createElement("br") + } + + fun createTextNode(data: String): DomCharacterData { + return document.createTextNode(data) + } + + fun assign(url: String) { + DomWindow.getWindow().location.assign(url) + } +} diff --git a/base/src/jsMain/kotlin/base/js/dom/DomBaseEvent.kt b/base/src/jsMain/kotlin/base/js/dom/DomBaseEvent.kt new file mode 100644 index 00000000000..dde5e46b071 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomBaseEvent.kt @@ -0,0 +1,11 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.dom.events.Event + +typealias DomBaseEvent = Event + +val DomBaseEvent.eventTarget: DomEventTarget? + get() = this.target + +val DomBaseEvent.currentEventTarget: DomEventTarget? + get() = this.currentTarget diff --git a/base/src/jsMain/kotlin/base/js/dom/DomCharacterData.kt b/base/src/jsMain/kotlin/base/js/dom/DomCharacterData.kt new file mode 100644 index 00000000000..def2b5ca65e --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomCharacterData.kt @@ -0,0 +1,5 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.dom.CharacterData + +typealias DomCharacterData = CharacterData diff --git a/base/src/jsMain/kotlin/base/js/dom/DomCollection.kt b/base/src/jsMain/kotlin/base/js/dom/DomCollection.kt new file mode 100644 index 00000000000..dffe0f0d254 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomCollection.kt @@ -0,0 +1,5 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.dom.ItemArrayLike + +typealias DomCollection = ItemArrayLike diff --git a/base/src/jsMain/kotlin/base/js/dom/DomCollectionIterator.kt b/base/src/jsMain/kotlin/base/js/dom/DomCollectionIterator.kt new file mode 100644 index 00000000000..6b7012ae1aa --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomCollectionIterator.kt @@ -0,0 +1,13 @@ +package jetbrains.datalore.base.js.dom + +class DomCollectionIterator internal constructor(private val myDomCollection: DomCollection) : Iterator { + private var myIndex = 0 + + override fun hasNext(): Boolean { + return myIndex < myDomCollection.length + } + + override fun next(): TypeT { + return myDomCollection.item(myIndex++)!! + } +} diff --git a/base/src/jsMain/kotlin/base/js/dom/DomConsole.kt b/base/src/jsMain/kotlin/base/js/dom/DomConsole.kt new file mode 100644 index 00000000000..c85df2ba244 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomConsole.kt @@ -0,0 +1,5 @@ +package jetbrains.datalore.base.js.dom + +import kotlin.js.Console + +typealias DomConsole = Console diff --git a/base/src/jsMain/kotlin/base/js/dom/DomContext2d.kt b/base/src/jsMain/kotlin/base/js/dom/DomContext2d.kt new file mode 100644 index 00000000000..2255b99639e --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomContext2d.kt @@ -0,0 +1,5 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.dom.CanvasRenderingContext2D + +typealias DomContext2d = CanvasRenderingContext2D diff --git a/base/src/jsMain/kotlin/base/js/dom/DomDataTransfer.kt b/base/src/jsMain/kotlin/base/js/dom/DomDataTransfer.kt new file mode 100644 index 00000000000..f9926485a51 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomDataTransfer.kt @@ -0,0 +1,5 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.dom.DataTransfer + +typealias DomDataTransfer = DataTransfer \ No newline at end of file diff --git a/base/src/jsMain/kotlin/base/js/dom/DomDocument.kt b/base/src/jsMain/kotlin/base/js/dom/DomDocument.kt new file mode 100644 index 00000000000..6a5c4974ec9 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomDocument.kt @@ -0,0 +1,11 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.dom.Document + +typealias DomDocument = Document + +val DomDocument.clientWidth: Int + get() = body!!.clientWidth + +val DomDocument.clientHeight: Int + get() = body!!.clientHeight diff --git a/base/src/jsMain/kotlin/base/js/dom/DomDragEvent.kt b/base/src/jsMain/kotlin/base/js/dom/DomDragEvent.kt new file mode 100644 index 00000000000..431ec9bbd0b --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomDragEvent.kt @@ -0,0 +1,5 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.dom.DragEvent + +typealias DomDragEvent = DragEvent diff --git a/base/src/jsMain/kotlin/base/js/dom/DomElement.kt b/base/src/jsMain/kotlin/base/js/dom/DomElement.kt new file mode 100644 index 00000000000..dc1002a7bd3 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomElement.kt @@ -0,0 +1,67 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.dom.Element + +typealias DomElement = Element + +fun DomElement.hasClassName(className: String): Boolean { + return classList.contains(className) +} + +fun DomElement.removeClassName(className: String) { + classList.remove(className) +} + +fun DomElement.addClassName(className: String) { + classList.add(className) +} + +fun DomElement.addClassNames(vararg classNames: String) { + classList.add(*classNames) +} + +fun DomElement.replaceClassName(oldClassName: String, newClassName: String) { + classList.remove(oldClassName) + classList.add(newClassName) +} + +val DomElement.childElements: DomElementList + get() = this.children + +val DomElement.childElementsList: List + get() = DomList(this) + +val DomElement.firstChildElement: DomElement? + get() = this.firstElementChild + +fun DomElement.setDisabled(state: Boolean) { + if (state) { + setAttribute("disabled", "disabled") + } else { + removeAttribute("disabled") + } +} + +fun DomElement.setTabIndex(index: Int) { + setAttribute("tabindex", index.toString()) +} + +fun DomElement.clearTabIndex() { + removeAttribute("tabindex") +} + +fun DomElement.getAbsoluteLeft(): Double { + return getBoundingClientRect().left +} + +fun DomElement.getAbsoluteRight(): Double { + return getBoundingClientRect().right +} + +fun DomElement.getAbsoluteTop(): Double { + return getBoundingClientRect().top +} + +fun DomElement.getAbsoluteBottom(): Double { + return getBoundingClientRect().bottom +} diff --git a/base/src/jsMain/kotlin/base/js/dom/DomElementList.kt b/base/src/jsMain/kotlin/base/js/dom/DomElementList.kt new file mode 100644 index 00000000000..d3e8e02f607 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomElementList.kt @@ -0,0 +1,16 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.dom.HTMLCollection + +typealias DomElementList = HTMLCollection + +val DomElementList.iterator: Iterator + get() = DomCollectionIterator(this) + +fun DomElementList.toIterable(): Iterable { + return object : Iterable { + override fun iterator(): Iterator { + return iterator + } + } +} diff --git a/base/src/jsMain/kotlin/base/js/dom/DomEventListener.kt b/base/src/jsMain/kotlin/base/js/dom/DomEventListener.kt new file mode 100644 index 00000000000..7ea9529f28e --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomEventListener.kt @@ -0,0 +1,10 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.dom.events.Event +import org.w3c.dom.events.EventListener + +class DomEventListener(private val handler: (EventT) -> Boolean) : EventListener { + override fun handleEvent(event: Event) { + handler(Event as EventT) + } +} diff --git a/base/src/jsMain/kotlin/base/js/dom/DomEventTarget.kt b/base/src/jsMain/kotlin/base/js/dom/DomEventTarget.kt new file mode 100644 index 00000000000..c3b8faae8e9 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomEventTarget.kt @@ -0,0 +1,80 @@ +package jetbrains.datalore.base.js.dom + +import jetbrains.datalore.base.registration.Registration +import org.w3c.dom.events.EventTarget + +typealias DomEventTarget = EventTarget + +fun DomEventTarget.onEvent(type: DomEventType, listener: DomEventListener): Registration { + return onEvent(type, listener, false) +} + +fun DomEventTarget.onEvent(type: DomEventType, listener: DomEventListener, capture: Boolean): Registration { + addEventListener(type.name, listener, capture) + return object : Registration() { + override fun doRemove() { + removeEventListener(type.name, listener) + } + } +} + +fun DomEventTarget.onClick(handler: () -> Unit): Registration { + return on(DomEventType.CLICK, handler) +} + +fun DomEventTarget.onClick(handler: (DomMouseEvent) -> Unit): Registration { + return on(DomEventType.CLICK, handler) +} + +fun DomEventTarget.onClick(handler: (DomMouseEvent) -> Boolean): Registration { + return on(DomEventType.CLICK, handler) +} + +fun DomEventTarget.on(event: DomEventType, runner: () -> Unit): Registration { + val consumer: (EventT) -> Unit = { runner() } + return on(event, consumer) +} + +fun DomEventTarget.on(event: DomEventType, consumer: (EventT) -> Unit): Registration { + val handler: (EventT) -> Boolean = { + consumer(it) + true + } + return on(event, handler) +} + +fun DomEventTarget.on(event: DomEventType, handler: (EventT) -> Boolean): Registration { + return onEvent(event, DomEventListener { evt: EventT -> + val result = handler(evt) + if (!result) { + evt.preventDefault() + evt.stopPropagation() + } + result + }, false) +} + +fun DomEventTarget.on(event: DomEventType, selector: String, handler: (EventT) -> Boolean): Registration { + return onEvent(event, DomEventListener { evt: EventT -> + var result = true + val node: DomNode = evt.eventTarget!!.cast() + + if (node.nodeType == DomNodeTypes.ELEMENT_NODE.toShort()) { + val element: DomElement = node.cast() + + if (element.closest(selector) != null) { + result = handler(evt) + } + } + + if (!result) { + evt.preventDefault() + evt.stopPropagation() + } + result + }, false) +} + +fun DomEventTarget.cast(): T { + return this as T +} diff --git a/base/src/jsMain/kotlin/base/js/dom/DomEventType.kt b/base/src/jsMain/kotlin/base/js/dom/DomEventType.kt new file mode 100644 index 00000000000..9a1cd3b3335 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomEventType.kt @@ -0,0 +1,53 @@ +package jetbrains.datalore.base.js.dom + +class DomEventType private constructor(internal val name: String) { + companion object { + + val BLUR = DomEventType("blur") + val CHANGE = DomEventType("change") + val INPUT = DomEventType("input") + val PASTE = DomEventType("paste") + val RESIZE = DomEventType("resize") + val CLICK = DomEventType("click") + val CONTEXT_MENU = DomEventType("contextmenu") + val DOUBLE_CLICK = DomEventType("dblclick") + val DRAG = DomEventType("drag") + val DRAG_END = DomEventType("dragend") + val DRAG_ENTER = DomEventType("dragenter") + val DRAG_LEAVE = DomEventType("dragleave") + val DRAG_OVER = DomEventType("dragover") + val DRAG_START = DomEventType("dragstart") + val DROP = DomEventType("drop") + val FOCUS = DomEventType("focus") + val FOCUS_IN = DomEventType("focusin") + val FOCUS_OUT = DomEventType("focusout") + val KEY_DOWN = DomEventType("keydown") + val KEY_PRESS = DomEventType("keypress") + val KEY_UP = DomEventType("keyup") + val LOAD = DomEventType("load") + val MOUSE_ENTER = DomEventType("mouseenter") + val MOUSE_LEAVE = DomEventType("mouseleave") + val MOUSE_DOWN = DomEventType("mousedown") + val MOUSE_MOVE = DomEventType("mousemove") + val MOUSE_OUT = DomEventType("mouseout") + val MOUSE_OVER = DomEventType("mouseover") + val MOUSE_UP = DomEventType("mouseup") + val MOUSE_WHEEL = DomEventType("wheel") + val SCROLL = DomEventType("scroll") + val TOUCH_CANCEL = DomEventType("touchcancel") + val TOUCH_END = DomEventType("touchend") + val TOUCH_MOVE = DomEventType("touchmove") + val TOUCH_START = DomEventType("touchstart") + val COMPOSITION_START = DomEventType("compositionstart") + val COMPOSITION_END = DomEventType("compositionend") + val COMPOSITION_UPDATE = DomEventType("compositionupdate") + val MESSAGE = DomEventType("message") + + val XHR_PROGRESS = DomEventType("progress") + val XHR_LOAD = DomEventType("load") + val XHR_LOAD_START = DomEventType("loadstart") + val XHR_LOAD_END = DomEventType("loadend") + val XHR_ABORT = DomEventType("abort") + val XHR_ERROR = DomEventType("error") + } +} diff --git a/base/src/jsMain/kotlin/base/js/dom/DomHTMLCanvasElement.kt b/base/src/jsMain/kotlin/base/js/dom/DomHTMLCanvasElement.kt new file mode 100644 index 00000000000..0e585f895f0 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomHTMLCanvasElement.kt @@ -0,0 +1,9 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.dom.CanvasRenderingContext2D +import org.w3c.dom.HTMLCanvasElement + +typealias DomHTMLCanvasElement = HTMLCanvasElement + +val DomHTMLCanvasElement.context2d: DomContext2d + get() = this.getContext("2d") as CanvasRenderingContext2D diff --git a/base/src/jsMain/kotlin/base/js/dom/DomHTMLElement.kt b/base/src/jsMain/kotlin/base/js/dom/DomHTMLElement.kt new file mode 100644 index 00000000000..5e7e108eb0c --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomHTMLElement.kt @@ -0,0 +1,5 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.dom.HTMLElement + +typealias DomHTMLElement = HTMLElement diff --git a/base/src/jsMain/kotlin/base/js/dom/DomImmediatePolyfill.kt b/base/src/jsMain/kotlin/base/js/dom/DomImmediatePolyfill.kt new file mode 100644 index 00000000000..d7dfaeecc26 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomImmediatePolyfill.kt @@ -0,0 +1,29 @@ +package jetbrains.datalore.base.js.dom + +import jetbrains.datalore.base.registration.Registration +import kotlin.random.Random + +class DomImmediatePolyfill { + + private val myId: String = "set_immediate_" + Random.nextDouble() + private val myQueue = ArrayList<() -> Unit>() + private var myRegistration: Registration? = null + + private fun onMessage(event: DomMessageEvent) { + val data = event.data + if (myId == data && !myQueue.isEmpty()) { + val head = myQueue.removeAt(0) + head() + } + } + + fun setImmediate(runnable: () -> Unit) { + if (myRegistration == null) { + myRegistration = DomWindow.getWindow().on(DomEventType.MESSAGE, { value: DomMessageEvent -> + onMessage(value) + } as ((DomMessageEvent) -> Unit)) + } + myQueue.add(runnable) + DomWindow.getWindow().postMessage(myId, "*") + } +} diff --git a/base/src/jsMain/kotlin/base/js/dom/DomKeyEvent.kt b/base/src/jsMain/kotlin/base/js/dom/DomKeyEvent.kt new file mode 100644 index 00000000000..d94ef8d83ca --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomKeyEvent.kt @@ -0,0 +1,5 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.dom.events.KeyboardEvent + +typealias DomKeyEvent = KeyboardEvent diff --git a/base/src/jsMain/kotlin/base/js/dom/DomList.kt b/base/src/jsMain/kotlin/base/js/dom/DomList.kt new file mode 100644 index 00000000000..9f7e6dba4ea --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomList.kt @@ -0,0 +1,40 @@ +package jetbrains.datalore.base.js.dom + +class DomList internal constructor(private val myNode: TypeT) : AbstractMutableList() { + + override fun get(index: Int): TypeT { + return myNode.getChild(index)!!.cast() + } + + override operator fun set(index: Int, element: TypeT): TypeT { + if (element.parentElement != null) { + throw IllegalStateException() + } + + val child = get(index) + myNode.replaceChild(child, element) + return child + } + + override fun add(index: Int, element: TypeT) { + if (element.parentElement != null) { + throw IllegalStateException() + } + + if (index == 0) { + myNode.insertFirst(element) + } else { + val prev = get(index - 1) + myNode.insertAfter(element, prev) + } + } + + override fun removeAt(index: Int): TypeT { + val child = get(index) + myNode.removeChild(child) + return child + } + + override val size: Int + get() = myNode.getChildCount() +} diff --git a/base/src/jsMain/kotlin/base/js/dom/DomLocation.kt b/base/src/jsMain/kotlin/base/js/dom/DomLocation.kt new file mode 100644 index 00000000000..34c6975cdd5 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomLocation.kt @@ -0,0 +1,5 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.dom.Location + +typealias DomLocation = Location diff --git a/base/src/jsMain/kotlin/base/js/dom/DomMessageEvent.kt b/base/src/jsMain/kotlin/base/js/dom/DomMessageEvent.kt new file mode 100644 index 00000000000..ca9c65ef445 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomMessageEvent.kt @@ -0,0 +1,5 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.dom.MessageEvent + +typealias DomMessageEvent = MessageEvent diff --git a/base/src/jsMain/kotlin/base/js/dom/DomMouseButtons.kt b/base/src/jsMain/kotlin/base/js/dom/DomMouseButtons.kt new file mode 100644 index 00000000000..7dcc17511a0 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomMouseButtons.kt @@ -0,0 +1,8 @@ +package jetbrains.datalore.base.js.dom + +object DomMouseButtons { + + const val BUTTON_LEFT = 0 + const val BUTTON_MIDDLE = 1 + const val BUTTON_RIGHT = 2 +} diff --git a/base/src/jsMain/kotlin/base/js/dom/DomMouseEvent.kt b/base/src/jsMain/kotlin/base/js/dom/DomMouseEvent.kt new file mode 100644 index 00000000000..24ec409ab06 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomMouseEvent.kt @@ -0,0 +1,5 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.dom.events.MouseEvent + +typealias DomMouseEvent = MouseEvent diff --git a/base/src/jsMain/kotlin/base/js/dom/DomNode.kt b/base/src/jsMain/kotlin/base/js/dom/DomNode.kt new file mode 100644 index 00000000000..41a1c321e0d --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomNode.kt @@ -0,0 +1,55 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.dom.Node + +typealias DomNode = Node + +fun DomNode.insertFirst(child: DomNode) { + insertBefore(child, firstChild) +} + +fun DomNode.insertAfter(newChild: DomNode, refChild: DomNode?) { + val next = refChild?.nextSibling + if (next == null) { + appendChild(newChild) + } else { + insertBefore(newChild, next) + } +} + +fun DomNode.prepend(element: DomNode) { + if (getChildCount() == 0) { + appendChild(element) + } else { + val refChild = getChild(0) + insertBefore(element, refChild) + } +} + +fun DomNode.removeAllChildren() { + while (lastChild != null) { + removeChild(lastChild!!) + } +} + +fun DomNode.removeFromParent() { + val parent = parentElement + parent?.removeChild(this) +} + +fun DomNode.getChildCount(): Int { + return childNodes.length +} + +fun DomNode.getChildNodesList(): List { + return DomList(this) +} + +fun DomNode.getChild(index: Int): DomNode? { + return childNodes.item(index) +} + +fun DomNode.replaceWith(node: DomNode) { + val parent = parentNode ?: throw IllegalStateException("Parent node is null") + parent.replaceChild(node, this) +} diff --git a/base/src/jsMain/kotlin/base/js/dom/DomNodeList.kt b/base/src/jsMain/kotlin/base/js/dom/DomNodeList.kt new file mode 100644 index 00000000000..3f47bd1268a --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomNodeList.kt @@ -0,0 +1,16 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.dom.NodeList + +typealias DomNodeList = NodeList + +val DomNodeList.iterator: Iterator + get() = DomCollectionIterator(this) + +fun DomNodeList.toIterable(): Iterable { + return object : Iterable { + override fun iterator(): Iterator { + return iterator + } + } +} diff --git a/base/src/jsMain/kotlin/base/js/dom/DomNodeTypes.kt b/base/src/jsMain/kotlin/base/js/dom/DomNodeTypes.kt new file mode 100644 index 00000000000..3d7bcc08a8c --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomNodeTypes.kt @@ -0,0 +1,7 @@ +package jetbrains.datalore.base.js.dom + +object DomNodeTypes { + const val ELEMENT_NODE = 1 + const val TEXT_NODE = 3 + const val DOCUMENT_NODE = 9 +} diff --git a/base/src/jsMain/kotlin/base/js/dom/DomProgressEvent.kt b/base/src/jsMain/kotlin/base/js/dom/DomProgressEvent.kt new file mode 100644 index 00000000000..e49b2d3be0b --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomProgressEvent.kt @@ -0,0 +1,5 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.xhr.ProgressEvent + +typealias DomProgressEvent = ProgressEvent diff --git a/base/src/jsMain/kotlin/base/js/dom/DomRect.kt b/base/src/jsMain/kotlin/base/js/dom/DomRect.kt new file mode 100644 index 00000000000..1b0d152f444 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomRect.kt @@ -0,0 +1,5 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.dom.DOMRect + +typealias DomRect = DOMRect diff --git a/base/src/jsMain/kotlin/base/js/dom/DomTokenList.kt b/base/src/jsMain/kotlin/base/js/dom/DomTokenList.kt new file mode 100644 index 00000000000..c1359c58f81 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomTokenList.kt @@ -0,0 +1,5 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.dom.DOMTokenList + +typealias DomTokenList = DOMTokenList diff --git a/base/src/jsMain/kotlin/base/js/dom/DomWheelEvent.kt b/base/src/jsMain/kotlin/base/js/dom/DomWheelEvent.kt new file mode 100644 index 00000000000..9c1f3578ade --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomWheelEvent.kt @@ -0,0 +1,5 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.dom.events.WheelEvent + +typealias DomWheelEvent = WheelEvent diff --git a/base/src/jsMain/kotlin/base/js/dom/DomWindow.kt b/base/src/jsMain/kotlin/base/js/dom/DomWindow.kt new file mode 100644 index 00000000000..2b835509bf5 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/DomWindow.kt @@ -0,0 +1,86 @@ +package jetbrains.datalore.base.js.dom + +import jetbrains.datalore.base.js.css.StyleMap +import org.w3c.dom.Window +import kotlin.browser.document +import kotlin.browser.window + +class DomWindow(private val myWindow: Window) : DomEventTarget() { + + private var myImmediatePolyfill: DomImmediatePolyfill? = null + + val location: DomLocation + get() = myWindow.location + + val scrollX: Double + get() = myWindow.scrollX + + val scrollY: Double + get() = myWindow.scrollY + + val innerWidth: Int + get() = myWindow.innerWidth + + val innerHeight: Int + get() = myWindow.innerHeight + + val devicePixelRatio: Double + get() = myWindow.devicePixelRatio + + fun getComputedStyle(element: DomElement): StyleMap { + return myWindow.getComputedStyle(element) + } + + fun setImmediate(runnable: () -> Unit) { + if (myImmediatePolyfill == null) { + myImmediatePolyfill = DomImmediatePolyfill() + } + myImmediatePolyfill!!.setImmediate(runnable) + } + + + fun requestAnimationFrame(callback: (Double) -> Unit): Int { + return myWindow.requestAnimationFrame(callback) + } + + fun cancelAnimationFrame(requestId: Int) { + myWindow.cancelAnimationFrame(requestId) + } + + fun postMessage(message: String, targetOrigin: String) { + myWindow.postMessage(message, targetOrigin) + } + + fun scrollTo(x: Double, y: Double) { + myWindow.scrollTo(x, y) + } + + fun atob(data: String): String { + return myWindow.atob(data) + } + + fun btoa(str: String): String { + return myWindow.btoa(str) + } + + fun open(url: String, name: String, features: String): DomWindow? { + val win = myWindow.open(url, name, features) + return if (win != null) DomWindow(win) else null + } + + companion object { + private val domWindow = DomWindow(window) + + fun getWindow(): DomWindow { + return domWindow + } + + fun getDocument(): DomDocument { + return document + } + + fun getConsole(): DomConsole { + return console + } + } +} diff --git a/base/src/jsMain/kotlin/base/js/dom/ImageDomElement.kt b/base/src/jsMain/kotlin/base/js/dom/ImageDomElement.kt new file mode 100644 index 00000000000..462bd7327fa --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/ImageDomElement.kt @@ -0,0 +1,5 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.dom.HTMLImageElement + +typealias ImageDomElement = HTMLImageElement diff --git a/base/src/jsMain/kotlin/base/js/dom/InputDomElement.kt b/base/src/jsMain/kotlin/base/js/dom/InputDomElement.kt new file mode 100644 index 00000000000..5902844de2c --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/InputDomElement.kt @@ -0,0 +1,9 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.dom.HTMLInputElement + +typealias InputDomElement = HTMLInputElement + +fun InputDomElement.isChecked(): Boolean { + return checked +} diff --git a/base/src/jsMain/kotlin/base/js/dom/OptionDomElement.kt b/base/src/jsMain/kotlin/base/js/dom/OptionDomElement.kt new file mode 100644 index 00000000000..297e8acd308 --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/OptionDomElement.kt @@ -0,0 +1,5 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.dom.HTMLOptionElement + +typealias OptionDomElement = HTMLOptionElement diff --git a/base/src/jsMain/kotlin/base/js/dom/TextAreaDomElement.kt b/base/src/jsMain/kotlin/base/js/dom/TextAreaDomElement.kt new file mode 100644 index 00000000000..15aadc964ec --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/dom/TextAreaDomElement.kt @@ -0,0 +1,5 @@ +package jetbrains.datalore.base.js.dom + +import org.w3c.dom.HTMLTextAreaElement + +typealias TextAreaDomElement = HTMLTextAreaElement diff --git a/base/src/jsMain/kotlin/base/js/files/File.kt b/base/src/jsMain/kotlin/base/js/files/File.kt new file mode 100644 index 00000000000..eb34b5c333a --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/files/File.kt @@ -0,0 +1,3 @@ +package jetbrains.datalore.base.js.files + +typealias File = org.w3c.files.File diff --git a/base/src/jsMain/kotlin/base/js/files/FileList.kt b/base/src/jsMain/kotlin/base/js/files/FileList.kt new file mode 100644 index 00000000000..813d911929c --- /dev/null +++ b/base/src/jsMain/kotlin/base/js/files/FileList.kt @@ -0,0 +1,3 @@ +package jetbrains.datalore.base.js.files + +typealias FileList = org.w3c.files.FileList diff --git a/visualization-base-canvas/src/commonMain/kotlin/visualization/base/canvas/Context2d.kt b/visualization-base-canvas/src/commonMain/kotlin/visualization/base/canvas/Context2d.kt index c8df92599cc..a18d7ab02c2 100644 --- a/visualization-base-canvas/src/commonMain/kotlin/visualization/base/canvas/Context2d.kt +++ b/visualization-base-canvas/src/commonMain/kotlin/visualization/base/canvas/Context2d.kt @@ -6,7 +6,7 @@ import jetbrains.datalore.visualization.base.canvas.Canvas.Snapshot interface Context2d { fun clearRect(rect: DoubleRectangle) - fun drawImage(snapshot: Snapshot, x: Int, y: Int) + fun drawImage(snapshot: Snapshot, x: Double, y: Double) fun beginPath() fun closePath() fun stroke() diff --git a/visualization-base-canvas/src/commonMain/kotlin/visualization/base/canvas/ScaledContext2d.kt b/visualization-base-canvas/src/commonMain/kotlin/visualization/base/canvas/ScaledContext2d.kt index 146c71c340d..98a52ad8f06 100644 --- a/visualization-base-canvas/src/commonMain/kotlin/visualization/base/canvas/ScaledContext2d.kt +++ b/visualization-base-canvas/src/commonMain/kotlin/visualization/base/canvas/ScaledContext2d.kt @@ -3,14 +3,9 @@ package jetbrains.datalore.visualization.base.canvas import jetbrains.datalore.base.geometry.DoubleRectangle import jetbrains.datalore.base.geometry.DoubleVector import jetbrains.datalore.visualization.base.canvas.Canvas.Snapshot -import kotlin.math.ceil internal class ScaledContext2d(private val myContext2d: Context2d, private val myScale: Double) : Context2d { - private fun scaled(value: Int): Int { - return ceil(myScale * value).toInt() - } - private fun scaled(value: Double): Double { return myScale * value } @@ -34,7 +29,7 @@ internal class ScaledContext2d(private val myContext2d: Context2d, private val m return CssStyleUtil.scaleFont(font, myScale) } - override fun drawImage(snapshot: Snapshot, x: Int, y: Int) { + override fun drawImage(snapshot: Snapshot, x: Double, y: Double) { myContext2d.drawImage(snapshot, scaled(x), scaled(y)) } diff --git a/visualization-base-canvas/src/commonMain/kotlin/visualization/base/svgToCanvas/SvgCanvasRenderer.kt b/visualization-base-canvas/src/commonMain/kotlin/visualization/base/svgToCanvas/SvgCanvasRenderer.kt index 034894a4c0c..3c6ae598cdb 100644 --- a/visualization-base-canvas/src/commonMain/kotlin/visualization/base/svgToCanvas/SvgCanvasRenderer.kt +++ b/visualization-base-canvas/src/commonMain/kotlin/visualization/base/svgToCanvas/SvgCanvasRenderer.kt @@ -69,7 +69,7 @@ class SvgCanvasRenderer(private val svgRoot: SvgElement, private val canvasContr virtualCanvas.takeSnapshot().onSuccess { value -> drawLater(canvasControl) { clearCanvas(mainCanvas) - mainCanvas.context2d.drawImage(value, 0, 0) + mainCanvas.context2d.drawImage(value, 0.0, 0.0) } } } diff --git a/visualization-base-canvas/src/jsMain/kotlin/visualization/base/canvas/dom/DomAnimationTimer.kt b/visualization-base-canvas/src/jsMain/kotlin/visualization/base/canvas/dom/DomAnimationTimer.kt new file mode 100644 index 00000000000..7ad5e195138 --- /dev/null +++ b/visualization-base-canvas/src/jsMain/kotlin/visualization/base/canvas/dom/DomAnimationTimer.kt @@ -0,0 +1,48 @@ +package jetbrains.datalore.visualization.base.canvasDom + +import jetbrains.datalore.base.js.dom.DomElement +import jetbrains.datalore.base.js.dom.DomWindow +import jetbrains.datalore.visualization.base.canvas.CanvasControl.AnimationTimer + +internal abstract class DomAnimationTimer(private val myElement: DomElement) : AnimationTimer { + private var myHandle: Int? = null + private var myIsStarted: Boolean = false + + init { + myIsStarted = false + } + + internal abstract fun handle(millisTime: Long) + + override fun start() { + if (myIsStarted) { + return + } + + myIsStarted = true + requestNextFrame() + } + + override fun stop() { + if (!myIsStarted) { + return + } + + myIsStarted = false + DomWindow.getWindow().cancelAnimationFrame(myHandle!!) + } + + fun execute(millisTime: Double) { + if (!myIsStarted) { + return + } + + handle(millisTime.toLong()) + + requestNextFrame() + } + + private fun requestNextFrame() { + myHandle = DomWindow.getWindow().requestAnimationFrame { this.execute(it) } + } +} diff --git a/visualization-base-canvas/src/jsMain/kotlin/visualization/base/canvas/dom/DomCanvas.kt b/visualization-base-canvas/src/jsMain/kotlin/visualization/base/canvas/dom/DomCanvas.kt new file mode 100644 index 00000000000..e81fffd43f5 --- /dev/null +++ b/visualization-base-canvas/src/jsMain/kotlin/visualization/base/canvas/dom/DomCanvas.kt @@ -0,0 +1,42 @@ +package jetbrains.datalore.visualization.base.canvasDom + +import jetbrains.datalore.base.async.Async +import jetbrains.datalore.base.async.Asyncs +import jetbrains.datalore.base.geometry.Vector +import jetbrains.datalore.base.js.css.setHeight +import jetbrains.datalore.base.js.css.setWidth +import jetbrains.datalore.base.js.dom.DomApi +import jetbrains.datalore.base.js.dom.DomHTMLCanvasElement +import jetbrains.datalore.base.js.dom.DomWindow +import jetbrains.datalore.base.js.dom.context2d +import jetbrains.datalore.visualization.base.canvas.Canvas +import jetbrains.datalore.visualization.base.canvas.ScaledCanvas +import kotlin.math.ceil + +internal class DomCanvas private constructor(val domHTMLCanvasElement: DomHTMLCanvasElement, size: Vector, pixelRatio: Double) + : ScaledCanvas(DomContext2d(domHTMLCanvasElement.context2d), size, pixelRatio) { + + init { + domHTMLCanvasElement.style.setWidth(size.x) + domHTMLCanvasElement.style.setHeight(size.y) + domHTMLCanvasElement.width = ceil(size.x * pixelRatio).toInt() + domHTMLCanvasElement.height = ceil(size.y * pixelRatio).toInt() + } + + override fun takeSnapshot(): Async { + return Asyncs.constant(DomSnapshot()) + } + + internal inner class DomSnapshot : Canvas.Snapshot { + val canvasElement: DomHTMLCanvasElement + get() = domHTMLCanvasElement + } + + companion object { + private val DEVICE_PIXEL_RATIO = DomWindow.getWindow().devicePixelRatio + + fun create(size: Vector): DomCanvas { + return DomCanvas(DomApi.createCanvas(), size, DEVICE_PIXEL_RATIO) + } + } +} diff --git a/visualization-base-canvas/src/jsMain/kotlin/visualization/base/canvas/dom/DomCanvasControl.kt b/visualization-base-canvas/src/jsMain/kotlin/visualization/base/canvas/dom/DomCanvasControl.kt new file mode 100644 index 00000000000..80a5774b296 --- /dev/null +++ b/visualization-base-canvas/src/jsMain/kotlin/visualization/base/canvas/dom/DomCanvasControl.kt @@ -0,0 +1,47 @@ +package jetbrains.datalore.visualization.base.canvasDom + +import jetbrains.datalore.base.event.MouseEvent +import jetbrains.datalore.base.geometry.Vector +import jetbrains.datalore.base.js.css.enumerables.CssPosition +import jetbrains.datalore.base.js.css.setPosition +import jetbrains.datalore.base.js.dom.DomApi +import jetbrains.datalore.base.js.dom.DomHTMLElement +import jetbrains.datalore.base.observable.event.EventHandler +import jetbrains.datalore.base.registration.Registration +import jetbrains.datalore.visualization.base.canvas.Canvas +import jetbrains.datalore.visualization.base.canvas.CanvasControl + +class DomCanvasControl(override val size: Vector) : CanvasControl { + val rootElement: DomHTMLElement = DomApi.createDiv() as DomHTMLElement + + init { + rootElement.style.setPosition(CssPosition.RELATIVE) + } + + override fun createAnimationTimer(eventHandler: CanvasControl.AnimationEventHandler): CanvasControl.AnimationTimer { + return object : DomAnimationTimer(rootElement) { + override fun handle(millisTime: Long) { + eventHandler.onEvent(millisTime) + } + } + } + + override fun addMouseEventHandler(eventSpec: CanvasControl.EventSpec, eventHandler: EventHandler): Registration { + return DomCanvasUtil.addMouseEventHandler(rootElement, eventSpec, eventHandler) + } + + override fun createCanvas(size: Vector): Canvas { + val domCanvas = DomCanvas.create(size) + domCanvas.domHTMLCanvasElement.style.setPosition(CssPosition.ABSOLUTE) + return domCanvas + } + + override fun addChildren(canvas: Canvas) { + rootElement.appendChild((canvas as DomCanvas).domHTMLCanvasElement) + } + + override fun removeChild(canvas: Canvas) { + rootElement.removeChild((canvas as DomCanvas).domHTMLCanvasElement) + + } +} diff --git a/visualization-base-canvas/src/jsMain/kotlin/visualization/base/canvas/dom/DomCanvasUtil.kt b/visualization-base-canvas/src/jsMain/kotlin/visualization/base/canvas/dom/DomCanvasUtil.kt new file mode 100644 index 00000000000..fc6d04db061 --- /dev/null +++ b/visualization-base-canvas/src/jsMain/kotlin/visualization/base/canvas/dom/DomCanvasUtil.kt @@ -0,0 +1,41 @@ +package jetbrains.datalore.visualization.base.canvasDom + +import jetbrains.datalore.base.event.MouseEvent +import jetbrains.datalore.base.event.dom.DomEventUtil.getButton +import jetbrains.datalore.base.event.dom.DomEventUtil.getModifiers +import jetbrains.datalore.base.js.dom.* +import jetbrains.datalore.base.observable.event.EventHandler +import jetbrains.datalore.base.registration.Registration +import jetbrains.datalore.visualization.base.canvas.CanvasControl.EventSpec + +internal object DomCanvasUtil { + private val EVENT_SPEC_MAP = mapOf( + EventSpec.MOUSE_ENTERED to DomEventType.MOUSE_ENTER, + EventSpec.MOUSE_LEFT to DomEventType.MOUSE_LEAVE, + EventSpec.MOUSE_MOVED to DomEventType.MOUSE_MOVE, + EventSpec.MOUSE_DRAGGED to DomEventType.DRAG_OVER, + EventSpec.MOUSE_CLICKED to DomEventType.CLICK, + EventSpec.MOUSE_DOUBLE_CLICKED to DomEventType.DOUBLE_CLICK, + EventSpec.MOUSE_PRESSED to DomEventType.MOUSE_DOWN, + EventSpec.MOUSE_RELEASED to DomEventType.MOUSE_UP + ) + + fun addMouseEventHandler(domElement: DomElement, eventSpec: EventSpec, eventHandler: EventHandler): Registration { + return domElement.onEvent( + EVENT_SPEC_MAP.getValue(eventSpec), + convertEventHandler(eventHandler, eventSpec), + true + ) + } + + private fun convertEventHandler(handler: EventHandler, eventSpec: EventSpec): DomEventListener { + return DomEventListener { + handler.onEvent(createMouseEvent(it)) + false + } + } + + private fun createMouseEvent(e: DomMouseEvent): MouseEvent { + return MouseEvent(e.clientX, e.clientY, getButton(e), getModifiers(e)) + } +} diff --git a/visualization-base-canvas/src/jsMain/kotlin/visualization/base/canvas/dom/DomContext2d.kt b/visualization-base-canvas/src/jsMain/kotlin/visualization/base/canvas/dom/DomContext2d.kt new file mode 100644 index 00000000000..5bdae753fbc --- /dev/null +++ b/visualization-base-canvas/src/jsMain/kotlin/visualization/base/canvas/dom/DomContext2d.kt @@ -0,0 +1,192 @@ +package jetbrains.datalore.visualization.base.canvasDom + +import jetbrains.datalore.base.geometry.DoubleRectangle +import jetbrains.datalore.base.geometry.DoubleVector +import jetbrains.datalore.base.js.css.enumerables.CssLineCap +import jetbrains.datalore.base.js.css.enumerables.CssLineJoin +import jetbrains.datalore.base.js.css.enumerables.CssTextAlign +import jetbrains.datalore.base.js.css.enumerables.CssTextBaseLine +import jetbrains.datalore.visualization.base.canvas.Canvas.Snapshot +import jetbrains.datalore.visualization.base.canvas.Context2d +import jetbrains.datalore.visualization.base.canvas.CssFontParser +import jetbrains.datalore.visualization.base.canvasDom.DomCanvas.DomSnapshot +import org.w3c.dom.* + +internal class DomContext2d(private val myContext2d: CanvasRenderingContext2D) : Context2d { + private fun convertLineJoin(lineJoin: Context2d.LineJoin): CssLineJoin { + return when (lineJoin) { + Context2d.LineJoin.BEVEL -> CssLineJoin.BEVEL + Context2d.LineJoin.MITER -> CssLineJoin.MITER + Context2d.LineJoin.ROUND -> CssLineJoin.ROUND + } + } + + private fun convertLineCap(lineCap: Context2d.LineCap): CssLineCap { + return when (lineCap) { + Context2d.LineCap.BUTT -> CssLineCap.BUTT + Context2d.LineCap.ROUND -> CssLineCap.ROUND + Context2d.LineCap.SQUARE -> CssLineCap.SQUARE + } + } + + private fun convertTextBaseline(baseline: Context2d.TextBaseline): CssTextBaseLine { + return when (baseline) { + Context2d.TextBaseline.ALPHABETIC -> CssTextBaseLine.ALPHABETIC + Context2d.TextBaseline.BOTTOM -> CssTextBaseLine.BOTTOM + Context2d.TextBaseline.HANGING -> CssTextBaseLine.HANGING + Context2d.TextBaseline.IDEOGRAPHIC -> CssTextBaseLine.IDEOGRAPHIC + Context2d.TextBaseline.MIDDLE -> CssTextBaseLine.MIDDLE + Context2d.TextBaseline.TOP -> CssTextBaseLine.TOP + } + } + + private fun convertTextAlign(align: Context2d.TextAlign): CssTextAlign { + return when (align) { + Context2d.TextAlign.CENTER -> CssTextAlign.CENTER + Context2d.TextAlign.END -> CssTextAlign.END + Context2d.TextAlign.LEFT -> CssTextAlign.LEFT + Context2d.TextAlign.RIGHT -> CssTextAlign.RIGHT + Context2d.TextAlign.START -> CssTextAlign.START + } + } + + override fun drawImage(snapshot: Snapshot, x: Double, y: Double) { + val domSnapshot = snapshot as DomSnapshot + myContext2d.drawImage(domSnapshot.canvasElement, x, y) + } + + override fun beginPath() { + myContext2d.beginPath() + } + + override fun closePath() { + myContext2d.closePath() + } + + override fun stroke() { + myContext2d.stroke() + } + + override fun fill() { + myContext2d.fill(CanvasFillRule.NONZERO) + } + + override fun fillEvenOdd() { + myContext2d.fill(CanvasFillRule.EVENODD) + } + + override fun fillRect(x: Double, y: Double, w: Double, h: Double) { + myContext2d.fillRect(x, y, w, h) + } + + override fun moveTo(x: Double, y: Double) { + myContext2d.moveTo(x, y) + } + + override fun lineTo(x: Double, y: Double) { + myContext2d.lineTo(x, y) + } + + override fun arc(x: Double, y: Double, radius: Double, startAngle: Double, endAngle: Double) { + myContext2d.arc(x, y, radius, startAngle, endAngle) + } + + override fun save() { + myContext2d.save() + } + + override fun restore() { + myContext2d.restore() + } + + override fun setFillColor(color: String?) { + myContext2d.fillStyle = color + } + + override fun setStrokeColor(color: String?) { + myContext2d.strokeStyle = color + } + + override fun setGlobalAlpha(alpha: Double) { + myContext2d.globalAlpha = alpha + } + + override fun setFont(f: String) { + myContext2d.font = f + } + + override fun setLineWidth(lineWidth: Double) { + myContext2d.lineWidth = lineWidth + } + + override fun strokeRect(x: Double, y: Double, w: Double, h: Double) { + myContext2d.strokeRect(x, y, w, h) + } + + override fun strokeText(text: String, x: Double, y: Double) { + myContext2d.strokeText(text, x, y) + } + + override fun fillText(text: String, x: Double, y: Double) { + myContext2d.fillText(text, x, y) + } + + override fun scale(x: Double, y: Double) { + myContext2d.scale(x, y) + } + + override fun rotate(angle: Double) { + myContext2d.rotate(angle) + } + + override fun translate(x: Double, y: Double) { + myContext2d.translate(x, y) + } + + override fun transform(m11: Double, m12: Double, m21: Double, m22: Double, dx: Double, dy: Double) { + myContext2d.transform(m11, m12, m21, m22, dx, dy) + } + + override fun bezierCurveTo(cp1x: Double, cp1y: Double, cp2x: Double, cp2y: Double, x: Double, y: Double) { + myContext2d.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) + } + + override fun quadraticCurveTo(cpx: Double, cpy: Double, x: Double, y: Double) { + myContext2d.quadraticCurveTo(cpx, cpy, x, y) + } + + override fun setLineJoin(lineJoin: Context2d.LineJoin) { + myContext2d.lineJoin = convertLineJoin(lineJoin) + } + + override fun setLineCap(lineCap: Context2d.LineCap) { + myContext2d.lineCap = convertLineCap(lineCap) + } + + override fun setTextBaseline(baseline: Context2d.TextBaseline) { + myContext2d.textBaseline = convertTextBaseline(baseline) + } + + override fun setTextAlign(align: Context2d.TextAlign) { + myContext2d.textAlign = convertTextAlign(align) + } + + override fun setTransform(m11: Double, m12: Double, m21: Double, m22: Double, dx: Double, dy: Double) { + myContext2d.setTransform(m11, m12, m21, m22, dx, dy) + } + + override fun setLineDash(lineDash: DoubleArray) { + myContext2d.setLineDash(lineDash.toTypedArray()) + } + + override fun measureText(str: String, font: String): DoubleVector { + val parser = CssFontParser.create(font) ?: throw IllegalStateException("Could not parse css font string: $font") + val height = parser.fontSize ?: 10.0 + val width = myContext2d.measureText(str).width + return DoubleVector(myContext2d.measureText(str).width, height) + } + + override fun clearRect(rect: DoubleRectangle) { + myContext2d.clearRect(rect.left, rect.top, rect.width, rect.height) + } +} diff --git a/visualization-base-canvas/src/jvmMain/kotlin/visualization/base/canvas/awt/AwtContext2d.kt b/visualization-base-canvas/src/jvmMain/kotlin/visualization/base/canvas/awt/AwtContext2d.kt index 3207cdccf1d..644d54d4bf8 100644 --- a/visualization-base-canvas/src/jvmMain/kotlin/visualization/base/canvas/awt/AwtContext2d.kt +++ b/visualization-base-canvas/src/jvmMain/kotlin/visualization/base/canvas/awt/AwtContext2d.kt @@ -27,12 +27,12 @@ internal class AwtContext2d(graphics2D: Graphics2D) : Context2d { restore() } - override fun drawImage(snapshot: Snapshot, x: Int, y: Int) { + override fun drawImage(snapshot: Snapshot, x: Double, y: Double) { val awtSnapshot = snapshot as AwtSnapshot current().drawImage( awtSnapshot.image, - x, y, + x.toInt(), y.toInt(), awtSnapshot.size.x, awtSnapshot.size.y ) { img, infoflags, x1, y1, width, height -> false } } diff --git a/visualization-base-canvas/src/jvmMain/kotlin/visualization/base/canvas/javaFx/JavafxContext2d.kt b/visualization-base-canvas/src/jvmMain/kotlin/visualization/base/canvas/javaFx/JavafxContext2d.kt index 7f4cf559bfb..7e042a73269 100644 --- a/visualization-base-canvas/src/jvmMain/kotlin/visualization/base/canvas/javaFx/JavafxContext2d.kt +++ b/visualization-base-canvas/src/jvmMain/kotlin/visualization/base/canvas/javaFx/JavafxContext2d.kt @@ -61,7 +61,7 @@ internal class JavafxContext2d(private val myContext2d: GraphicsContext) : Conte return if (size == null) Font.font(family) else Font.font(family, size) } - override fun drawImage(snapshot: Snapshot, x: Int, y: Int) { + override fun drawImage(snapshot: Snapshot, x: Double, y: Double) { val javafxSnapshot = snapshot as JavafxCanvas.JavafxSnapshot myContext2d.drawImage(javafxSnapshot.image, x.toDouble(), y.toDouble()) }