craft-native 0.0.162 → 0.0.164

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -88,6 +88,7 @@ import java.util.UUID
88
88
  import kotlin.math.max
89
89
  import kotlin.math.min
90
90
  import kotlin.math.PI
91
+ import kotlin.math.ceil
91
92
  import kotlin.math.roundToInt
92
93
  import kotlin.math.roundToLong
93
94
  import kotlin.math.sin
@@ -202,13 +203,16 @@ private class CraftNativeSvgView(context: Context) : View(context) {
202
203
  props.has(name) && !props.isNull(name) -> props.optString(name)
203
204
  else -> null
204
205
  }
206
+ // Keep the cascade explicit: computed style wins over a raw prop
207
+ // when both provide the same SVG paint value.
205
208
  val weight = value("fontWeight")
209
+ val ownOpacity = value("opacity")?.toFloatOrNull() ?: 1f
206
210
  return copy(
207
211
  fill = value("fill") ?: fill,
208
212
  stroke = value("stroke") ?: stroke,
209
213
  strokeWidth = value("strokeWidth")?.toFloatOrNull() ?: strokeWidth,
210
214
  color = value("color") ?: color,
211
- opacity = (value("opacity")?.toFloatOrNull() ?: 1f) * opacity,
215
+ opacity = ownOpacity * opacity,
212
216
  typeface = if (weight in setOf("bold", "700")) Typeface.DEFAULT_BOLD else typeface,
213
217
  )
214
218
  }
@@ -393,8 +397,8 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
393
397
  private var alignItems = "stretch"
394
398
  private var alignContent = "flex-start"
395
399
  private var justifyContent = "flex-start"
396
- private var rowGap = 0
397
- private var columnGap = 0
400
+ private var rowGap = 0f
401
+ private var columnGap = 0f
398
402
  private var grid = false
399
403
  private var gridColumns = 1
400
404
  private var gridAutoRows: CraftNativeLength? = null
@@ -407,9 +411,9 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
407
411
  alignItems = style.optString("alignItems", "stretch")
408
412
  alignContent = style.optString("alignContent", "flex-start")
409
413
  justifyContent = style.optString("justifyContent", "flex-start")
410
- val gap = (style.optDouble("gap", 0.0) * density).toInt()
411
- rowGap = (style.optDouble("rowGap", gap.toDouble() / density.toDouble()) * density).toInt()
412
- columnGap = (style.optDouble("columnGap", gap.toDouble() / density.toDouble()) * density).toInt()
414
+ val gap = style.optDouble("gap", 0.0) * density
415
+ rowGap = style.optDouble("rowGap", gap / density.toDouble()).toFloat() * density
416
+ columnGap = style.optDouble("columnGap", gap / density.toDouble()).toFloat() * density
413
417
  grid = style.optString("display") == "grid"
414
418
  gridColumns = craftNativeGridColumnCount(
415
419
  if (style.has("gridTemplateColumns")) style.opt("gridTemplateColumns") else style.opt("gridColumns")
@@ -438,8 +442,8 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
438
442
  }
439
443
 
440
444
  private data class Entry(val view: View, val style: CraftNativeLayoutStyle, val width: Int, val height: Int)
441
- private data class Line(val entries: MutableList<Entry> = mutableListOf(), var main: Int = 0, var cross: Int = 0)
442
- private data class GridLayout(val rows: List<List<Entry>>, val absolute: List<Entry>, val cellWidth: Int, val rowHeights: List<Int>)
445
+ private data class Line(val entries: MutableList<Entry> = mutableListOf(), var main: Float = 0f, var cross: Float = 0f)
446
+ private data class GridLayout(val rows: List<List<Entry>>, val absolute: List<Entry>, val cellWidth: Float, val rowHeights: List<Float>)
443
447
 
444
448
  private fun entries(width: Int, height: Int): Pair<List<Line>, List<Entry>> {
445
449
  val availableMain = if (orientation == HORIZONTAL) max(0, width - paddingLeft - paddingRight)
@@ -487,8 +491,8 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
487
491
  }
488
492
  var line = Line()
489
493
  pending.forEachIndexed { index, entry ->
490
- val childMain = if (orientation == HORIZONTAL) entry.width + (margins[index * 2] + margins[index * 2 + 1]).roundToInt()
491
- else entry.height + (margins[index * 2] + margins[index * 2 + 1]).roundToInt()
494
+ val childMain = if (orientation == HORIZONTAL) entry.width.toFloat() + margins[index * 2] + margins[index * 2 + 1]
495
+ else entry.height.toFloat() + margins[index * 2] + margins[index * 2 + 1]
492
496
  val lp = entry.view.layoutParams as? MarginLayoutParams
493
497
  val crossMargins = if (orientation == HORIZONTAL) (lp?.topMargin ?: 0) + (lp?.bottomMargin ?: 0)
494
498
  else (lp?.leftMargin ?: 0) + (lp?.rightMargin ?: 0)
@@ -502,17 +506,22 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
502
506
  }
503
507
  line.entries += entry
504
508
  line.main = if (line.entries.size == 1) childMain else line.main + gap + childMain
505
- line.cross = max(line.cross, childCross)
509
+ line.cross = max(line.cross, childCross.toFloat())
506
510
  }
507
511
  if (line.entries.isNotEmpty()) lines += line
508
512
  }
513
+ // Reconciliation reverses the child order so the main axis runs in
514
+ // the requested direction. With wrapping, that also reverses the
515
+ // line order; flexbox keeps wrap lines in cross-axis order, so restore
516
+ // the groups while retaining the reversed entries inside each line.
517
+ if (reverse && wrap && lines.size > 1) lines.reverse()
509
518
  return lines to absolute
510
519
  }
511
520
 
512
521
  private fun gridLayout(width: Int, height: Int): GridLayout {
513
522
  val availableWidth = max(0, width - paddingLeft - paddingRight)
514
523
  val gap = columnGap
515
- val cellWidth = max(0, (availableWidth - max(0, gridColumns - 1) * gap) / max(1, gridColumns))
524
+ val cellWidth = max(0f, (availableWidth.toFloat() - max(0, gridColumns - 1) * gap) / max(1, gridColumns).toFloat())
516
525
  val rows = mutableListOf<MutableList<Entry>>()
517
526
  val absolute = mutableListOf<Entry>()
518
527
  for (index in 0 until childCount) {
@@ -532,7 +541,7 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
532
541
  absolute += Entry(child, style, modalWidth, modalHeight)
533
542
  continue
534
543
  }
535
- val childWidth = measureChildWidth(child, style, cellWidth, horizontalMargins)
544
+ val childWidth = measureChildWidth(child, style, cellWidth.roundToInt(), horizontalMargins)
536
545
  val childHeight = measureChildHeight(child, style, height - paddingTop - paddingBottom, verticalMargins)
537
546
  val entry = Entry(child, style, childWidth, childHeight)
538
547
  if (style.position == "absolute") absolute += entry
@@ -542,10 +551,10 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
542
551
  }
543
552
  }
544
553
  val rowHeights = rows.map { row ->
545
- max(gridAutoRows?.resolve(height - paddingTop - paddingBottom) ?: 0, row.maxOfOrNull { entry ->
554
+ max((gridAutoRows?.resolve(height - paddingTop - paddingBottom) ?: 0).toFloat(), row.maxOfOrNull { entry ->
546
555
  val lp = entry.view.layoutParams as? MarginLayoutParams
547
- entry.height + (lp?.topMargin ?: 0) + (lp?.bottomMargin ?: 0)
548
- } ?: 0)
556
+ (entry.height + (lp?.topMargin ?: 0) + (lp?.bottomMargin ?: 0)).toFloat()
557
+ } ?: 0f)
549
558
  }
550
559
  return GridLayout(rows, absolute, cellWidth, rowHeights)
551
560
  }
@@ -573,9 +582,9 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
573
582
  flowIndex += 1
574
583
  }
575
584
  val cellWidth = columnWidths.maxOrNull() ?: 0
576
- val width = paddingLeft + paddingRight + cellWidth * columns + max(0, columns - 1) * columnGap
585
+ val width = paddingLeft + paddingRight + cellWidth * columns + (max(0, columns - 1) * columnGap).roundToInt()
577
586
  val rowGapTotal = max(0, rowHeights.size - 1) * rowGap
578
- val measuredHeight = paddingTop + paddingBottom + rowHeights.sum() + rowGapTotal
587
+ val measuredHeight = paddingTop + paddingBottom + rowHeights.sum() + rowGapTotal.roundToInt()
579
588
  return width to measuredHeight
580
589
  }
581
590
 
@@ -658,15 +667,15 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
658
667
  }
659
668
 
660
669
  /** Keep the old-runtime shim on the same bounded-flex water-fill rule as the Zig bridge. */
661
- private fun fallbackFlexWidths(line: Line, available: Int, gap: Int): IntArray {
670
+ private fun fallbackFlexWidths(line: Line, available: Int, gap: Float): IntArray {
662
671
  val horizontal = orientation == HORIZONTAL
663
672
  val hypothetical = line.entries.sumOf { entry ->
664
673
  val lp = entry.view.layoutParams as? MarginLayoutParams
665
674
  val margins = if (horizontal) (lp?.leftMargin ?: 0) + (lp?.rightMargin ?: 0)
666
675
  else (lp?.topMargin ?: 0) + (lp?.bottomMargin ?: 0)
667
- (if (horizontal) entry.width else entry.height) + margins
668
- } + max(0, line.entries.size - 1) * gap
669
- val free = available - hypothetical
676
+ ((if (horizontal) entry.width else entry.height) + margins).toDouble()
677
+ } + max(0, line.entries.size - 1) * gap.toDouble()
678
+ val free = available.toDouble() - hypothetical
670
679
  val growTotal = line.entries.sumOf { it.style.flexGrow }
671
680
  val shrinkTotal = line.entries.sumOf {
672
681
  (it.style.flexShrink ?: defaultFlexShrink(it.view)) * (if (horizontal) it.width else it.height)
@@ -695,7 +704,7 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
695
704
  }
696
705
  }
697
706
  if (factor <= 0) return@repeat
698
- val remaining = available - fixed - activeBasis
707
+ val remaining = available.toDouble() - fixed - activeBasis
699
708
  var froze = false
700
709
  for ((index, entry) in line.entries.withIndex()) {
701
710
  if (frozen[index]) continue
@@ -733,16 +742,16 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
733
742
  return
734
743
  }
735
744
  val layout = gridLayout(width, height)
736
- val desiredWidth = paddingLeft + paddingRight + layout.cellWidth * gridColumns + max(0, gridColumns - 1) * columnGap
737
- val desiredHeight = paddingTop + paddingBottom + layout.rowHeights.sum() + max(0, layout.rowHeights.size - 1) * rowGap
745
+ val desiredWidth = paddingLeft + paddingRight + (layout.cellWidth * gridColumns + max(0, gridColumns - 1) * columnGap).roundToInt()
746
+ val desiredHeight = paddingTop + paddingBottom + layout.rowHeights.sum().roundToInt() + (max(0, layout.rowHeights.size - 1) * rowGap).roundToInt()
738
747
  setMeasuredDimension(resolveSize(desiredWidth, widthMeasureSpec), resolveSize(desiredHeight, heightMeasureSpec))
739
748
  return
740
749
  }
741
750
  val (lines, absolute) = entries(width, height)
742
- val main = lines.maxOfOrNull { it.main } ?: 0
743
- val cross = lines.sumOf { it.cross } + max(0, lines.size - 1) * (if (orientation == HORIZONTAL) rowGap else columnGap)
744
- val desiredWidth = if (orientation == HORIZONTAL) main else cross
745
- val desiredHeight = if (orientation == HORIZONTAL) cross else main
751
+ val main = lines.maxOfOrNull { it.main } ?: 0f
752
+ val cross = lines.sumOf { it.cross.toDouble() } + max(0, lines.size - 1) * (if (orientation == HORIZONTAL) rowGap else columnGap).toDouble()
753
+ val desiredWidth = if (orientation == HORIZONTAL) main.roundToInt() else cross.roundToInt()
754
+ val desiredHeight = if (orientation == HORIZONTAL) cross.roundToInt() else main.roundToInt()
746
755
  val absWidth = absolute.maxOfOrNull { it.width } ?: 0
747
756
  val absHeight = absolute.maxOfOrNull { it.height } ?: 0
748
757
  setMeasuredDimension(
@@ -760,12 +769,12 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
760
769
  // Older runtimes return null for the optional symbol; the local
761
770
  // layout below remains the compatibility path in that case.
762
771
  val sharedFrames = CraftNative.grid(
763
- contentWidth.toFloat(), contentHeight.toFloat(), gridColumns, columnGap.toFloat(), rowGap.toFloat(),
772
+ contentWidth.toFloat(), contentHeight.toFloat(), gridColumns, columnGap, rowGap,
764
773
  flow.map { it.width.toFloat() }.toFloatArray(), flow.map { it.height.toFloat() }.toFloatArray(),
765
- flow.map { it.style.width?.resolve(layout.cellWidth)?.toFloat() ?: Float.NaN }.toFloatArray(),
774
+ flow.map { it.style.width?.resolve(layout.cellWidth.roundToInt())?.toFloat() ?: Float.NaN }.toFloatArray(),
766
775
  flow.map { it.style.height?.resolve(null)?.toFloat() ?: Float.NaN }.toFloatArray(),
767
- flow.map { it.style.minWidth?.resolve(layout.cellWidth)?.toFloat() ?: Float.NaN }.toFloatArray(),
768
- flow.map { it.style.maxWidth?.resolve(layout.cellWidth)?.toFloat() ?: Float.NaN }.toFloatArray(),
776
+ flow.map { it.style.minWidth?.resolve(layout.cellWidth.roundToInt())?.toFloat() ?: Float.NaN }.toFloatArray(),
777
+ flow.map { it.style.maxWidth?.resolve(layout.cellWidth.roundToInt())?.toFloat() ?: Float.NaN }.toFloatArray(),
769
778
  flow.map { it.style.minHeight?.resolve(null)?.toFloat() ?: Float.NaN }.toFloatArray(),
770
779
  flow.map { it.style.maxHeight?.resolve(null)?.toFloat() ?: Float.NaN }.toFloatArray(),
771
780
  flow.flatMap {
@@ -790,29 +799,29 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
790
799
  }
791
800
  } else {
792
801
  val totalRows = layout.rowHeights.sum()
793
- val rowGaps = max(0, layout.rowHeights.size - 1) * rowGap
794
- val freeRows = max(0, contentHeight - totalRows - rowGaps)
802
+ val rowGaps = max(0, layout.rowHeights.size - 1) * rowGap.toFloat()
803
+ val freeRows = max(0f, contentHeight.toFloat() - totalRows - rowGaps)
795
804
  val rowCount = max(1, layout.rowHeights.size)
796
- var rowLeading = 0
797
- var rowBetween = rowGap
798
- var rowExtra = 0
805
+ var rowLeading = 0f
806
+ var rowBetween = rowGap.toFloat()
807
+ var rowExtra = 0f
799
808
  when (alignContentCode()) {
800
- 1 -> rowLeading = freeRows / 2
809
+ 1 -> rowLeading = freeRows / 2f
801
810
  2 -> rowLeading = freeRows
802
- 3 -> if (rowCount > 1) rowBetween += freeRows / (rowCount - 1)
811
+ 3 -> if (rowCount > 1) rowBetween += freeRows / (rowCount - 1).toFloat()
803
812
  4 -> {
804
- rowLeading = freeRows / (rowCount * 2)
805
- rowBetween += freeRows / rowCount
813
+ rowLeading = freeRows / (rowCount * 2).toFloat()
814
+ rowBetween += freeRows / rowCount.toFloat()
806
815
  }
807
816
  6 -> {
808
- rowLeading = freeRows / (rowCount + 1)
809
- rowBetween += freeRows / (rowCount + 1)
817
+ rowLeading = freeRows / (rowCount + 1).toFloat()
818
+ rowBetween += freeRows / (rowCount + 1).toFloat()
810
819
  }
811
- 5 -> rowExtra = freeRows / rowCount
820
+ 5 -> rowExtra = freeRows / rowCount.toFloat()
812
821
  }
813
- var rowTop = paddingTop + rowLeading
822
+ var rowTop = paddingTop.toFloat() + rowLeading
814
823
  for ((rowIndex, row) in layout.rows.withIndex()) {
815
- val rowHeight = layout.rowHeights[rowIndex] + rowExtra
824
+ val rowHeight = layout.rowHeights[rowIndex].toFloat() + rowExtra
816
825
  for ((column, entry) in row.withIndex()) {
817
826
  val align = entry.style.alignSelf ?: alignItems
818
827
  val lp = entry.view.layoutParams as? MarginLayoutParams
@@ -820,20 +829,20 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
820
829
  val marginRight = lp?.rightMargin ?: 0
821
830
  val marginTop = lp?.topMargin ?: 0
822
831
  val marginBottom = lp?.bottomMargin ?: 0
823
- val availableWidth = max(0, layout.cellWidth - marginLeft - marginRight)
824
- val availableHeight = max(0, rowHeight - marginTop - marginBottom)
825
- val childWidth = if (align == "stretch" && entry.style.width == null) availableWidth else min(availableWidth, entry.width)
832
+ val availableWidth = max(0f, layout.cellWidth - marginLeft - marginRight)
833
+ val availableHeight = max(0, (rowHeight - marginTop - marginBottom).roundToInt())
834
+ val childWidth = if (align == "stretch" && entry.style.width == null) availableWidth.roundToInt() else min(availableWidth.roundToInt(), entry.width)
826
835
  val childHeight = if (align == "stretch" && entry.style.height == null) availableHeight else min(availableHeight, entry.height)
827
- val x = paddingLeft + column * (layout.cellWidth + columnGap) + marginLeft + when (align) {
828
- "center" -> (availableWidth - childWidth) / 2
836
+ val x = (paddingLeft + column * (layout.cellWidth + columnGap) + marginLeft + when (align) {
837
+ "center" -> (availableWidth - childWidth) / 2f
829
838
  "flex-end" -> availableWidth - childWidth
830
- else -> 0
831
- }
832
- val y = rowTop + marginTop + when (align) {
839
+ else -> 0f
840
+ }).roundToInt()
841
+ val y = (rowTop + marginTop + when (align) {
833
842
  "center" -> (availableHeight - childHeight) / 2
834
843
  "flex-end" -> availableHeight - childHeight
835
844
  else -> 0
836
- }
845
+ }).roundToInt()
837
846
  entry.view.layout(x, y, x + childWidth, y + childHeight)
838
847
  }
839
848
  rowTop += rowHeight + rowBetween
@@ -857,42 +866,42 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
857
866
  val crossGap = if (orientation == HORIZONTAL) rowGap else columnGap
858
867
  val availableMain = if (orientation == HORIZONTAL) contentWidth else contentHeight
859
868
  val availableCross = if (orientation == HORIZONTAL) contentHeight else contentWidth
860
- val totalCross = lines.sumOf { it.cross } + max(0, lines.size - 1) * crossGap
861
- val crossFree = max(0, availableCross - totalCross)
862
- var crossLeading = 0
869
+ val totalCross = lines.sumOf { it.cross.toDouble() }.toFloat() + max(0, lines.size - 1) * crossGap
870
+ val crossFree = max(0f, availableCross.toFloat() - totalCross)
871
+ var crossLeading = 0f
863
872
  var crossBetween = crossGap
864
873
  val sharedCross = if (wrap && lines.size > 1) {
865
874
  CraftNative.flexLineDistribution(
866
- availableCross.toFloat(), crossGap.toFloat(), alignContentCode(), lines.map { it.cross.toFloat() }.toFloatArray()
875
+ availableCross.toFloat(), crossGap, alignContentCode(), lines.map { it.cross }.toFloatArray()
867
876
  )
868
877
  } else null
869
- sharedCross?.second?.forEachIndexed { index, size -> lines[index].cross = size.roundToInt() }
878
+ sharedCross?.second?.forEachIndexed { index, size -> lines[index].cross = size }
870
879
  if (wrap && lines.size > 1) {
871
880
  if (sharedCross == null) when (alignContent) {
872
- "center" -> crossLeading = crossFree / 2
881
+ "center" -> crossLeading = crossFree / 2f
873
882
  "flex-end" -> crossLeading = crossFree
874
- "space-between" -> crossBetween = crossFree / (lines.size - 1) + crossGap
883
+ "space-between" -> crossBetween = crossFree / (lines.size - 1).toFloat() + crossGap
875
884
  "space-around" -> {
876
- crossBetween = crossFree / lines.size + crossGap
877
- crossLeading = crossFree / (lines.size * 2)
885
+ crossBetween = crossFree / lines.size.toFloat() + crossGap
886
+ crossLeading = crossFree / (lines.size * 2).toFloat()
878
887
  }
879
888
  "space-evenly" -> {
880
- crossBetween = crossFree / (lines.size + 1) + crossGap
881
- crossLeading = crossFree / (lines.size + 1)
882
- }
883
- "stretch" -> {
884
- val extra = crossFree / lines.size
885
- lines.forEach { it.cross += extra }
889
+ crossBetween = crossFree / (lines.size + 1).toFloat() + crossGap
890
+ crossLeading = crossFree / (lines.size + 1).toFloat()
886
891
  }
887
892
  }
888
893
  }
889
- var crossOffset = (if (orientation == HORIZONTAL) paddingTop else paddingLeft) + crossLeading
894
+ val stretchExtra = if (sharedCross == null && wrap && lines.size > 1 && alignContent == "stretch")
895
+ crossFree / lines.size.toFloat()
896
+ else 0f
897
+ var crossOffset = (if (orientation == HORIZONTAL) paddingTop else paddingLeft).toFloat() + crossLeading
890
898
  for ((lineIndex, line) in lines.withIndex()) {
891
- val lineOffset = sharedCross?.first?.get(lineIndex)?.roundToInt()?.let { (if (orientation == HORIZONTAL) paddingTop else paddingLeft) + it } ?: crossOffset
899
+ val lineCross = line.cross.toFloat() + stretchExtra
900
+ val lineOffset = sharedCross?.first?.get(lineIndex)?.let { (if (orientation == HORIZONTAL) paddingTop else paddingLeft).toFloat() + it } ?: crossOffset
892
901
  val fallbackWidths = fallbackFlexWidths(line, availableMain, gap)
893
902
  val sharedPrimary = CraftNative.flexRow(
894
903
  availableMain.toFloat(),
895
- gap.toFloat(),
904
+ gap,
896
905
  justifyCode(),
897
906
  line.entries.map { entry -> if (orientation == HORIZONTAL) entry.width.toFloat() else entry.height.toFloat() }.toFloatArray(),
898
907
  line.entries.map { it.style.flexGrow.toFloat() }.toFloatArray(),
@@ -917,8 +926,8 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
917
926
  val lp = line.entries[index].view.layoutParams as? MarginLayoutParams
918
927
  val margins = if (orientation == HORIZONTAL) (lp?.leftMargin ?: 0) + (lp?.rightMargin ?: 0)
919
928
  else (lp?.topMargin ?: 0) + (lp?.bottomMargin ?: 0)
920
- sharedPrimary[index].roundToInt() + margins
921
- } + max(0, line.entries.size - 1) * gap
929
+ sharedPrimary[index].toDouble() + margins
930
+ } + max(0, line.entries.size - 1) * gap.toDouble()
922
931
  } else {
923
932
  line.entries.indices.sumOf { index ->
924
933
  val constrained = fallbackWidths[index]
@@ -926,49 +935,50 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
926
935
  val margins = if (orientation == HORIZONTAL) (lp?.leftMargin ?: 0) + (lp?.rightMargin ?: 0)
927
936
  else (lp?.topMargin ?: 0) + (lp?.bottomMargin ?: 0)
928
937
  constrained + margins
929
- } + max(0, line.entries.size - 1) * gap
938
+ }.toDouble() + max(0, line.entries.size - 1) * gap.toDouble()
930
939
  }
931
- val free = max(0, availableMain - distributedMain)
940
+ val free = max(0f, availableMain.toFloat() - distributedMain.toFloat())
932
941
  val (leading, between) = distribution(free, line.entries.size)
933
- var mainOffset = if (orientation == HORIZONTAL) paddingLeft else paddingTop
934
- mainOffset += leading
942
+ var mainOffset = (if (orientation == HORIZONTAL) paddingLeft else paddingTop).toFloat() + leading
935
943
  for ((index, entry) in line.entries.withIndex()) {
936
944
  val lp = entry.view.layoutParams as? MarginLayoutParams
937
945
  val marginStart = if (orientation == HORIZONTAL) lp?.leftMargin ?: 0 else lp?.topMargin ?: 0
938
946
  val marginEnd = if (orientation == HORIZONTAL) lp?.rightMargin ?: 0 else lp?.bottomMargin ?: 0
939
947
  val crossSize = if ((entry.style.alignSelf ?: alignItems) == "stretch" && (if (orientation == HORIZONTAL) entry.style.height else entry.style.width) == null)
940
- max(0, line.cross - (if (orientation == HORIZONTAL) (lp?.topMargin ?: 0) + (lp?.bottomMargin ?: 0) else (lp?.leftMargin ?: 0) + (lp?.rightMargin ?: 0)))
948
+ max(0, (lineCross - (if (orientation == HORIZONTAL) (lp?.topMargin ?: 0) + (lp?.bottomMargin ?: 0) else (lp?.leftMargin ?: 0) + (lp?.rightMargin ?: 0))).roundToInt())
941
949
  else if (orientation == HORIZONTAL) entry.height else entry.width
942
950
  val align = entry.style.alignSelf ?: alignItems
943
- val crossLeading = if (orientation == HORIZONTAL) lp?.topMargin ?: 0 else lp?.leftMargin ?: 0
944
- val crossTrailing = if (orientation == HORIZONTAL) lp?.bottomMargin ?: 0 else lp?.rightMargin ?: 0
951
+ val crossLeading = (if (orientation == HORIZONTAL) lp?.topMargin ?: 0 else lp?.leftMargin ?: 0).toFloat()
952
+ val crossTrailing = (if (orientation == HORIZONTAL) lp?.bottomMargin ?: 0 else lp?.rightMargin ?: 0).toFloat()
945
953
  val alignment = when (align) {
946
954
  "center" -> 1
947
955
  "flex-end" -> 2
948
956
  else -> 0
949
957
  }
950
958
  val crossPosition = CraftNative.crossAxisPosition(
951
- line.cross.toFloat(), crossSize.toFloat(), crossLeading.toFloat(), crossTrailing.toFloat(), alignment
952
- )?.roundToInt() ?: when (alignment) {
953
- 1 -> crossLeading + (line.cross - crossLeading - crossTrailing - crossSize) / 2
954
- 2 -> line.cross - crossTrailing - crossSize
959
+ lineCross, crossSize.toFloat(), crossLeading, crossTrailing, alignment
960
+ ) ?: when (alignment) {
961
+ 1 -> crossLeading + (lineCross - crossLeading - crossTrailing - crossSize) / 2f
962
+ 2 -> lineCross - crossTrailing - crossSize
955
963
  else -> crossLeading
956
964
  }
957
965
  // Shared positions cover every non-reverse justification; the
958
966
  // local main-axis rule remains the fallback for old runtimes
959
967
  // that return widths only.
960
- val sharedPosition = if (!reverse) sharedPositions?.get(index)?.roundToInt() else null
961
- val positionedMain = sharedPosition?.let { (if (orientation == HORIZONTAL) paddingLeft else paddingTop) + it }
968
+ // The shared result stores widths in the first half and
969
+ // main-axis positions in the second half.
970
+ val sharedPosition = if (!reverse) sharedPositions?.get(line.entries.size + index) else null
971
+ val positionedMain = sharedPosition?.let { (if (orientation == HORIZONTAL) paddingLeft else paddingTop).toFloat() + it }
962
972
  ?: mainOffset + marginStart
963
- val x = if (orientation == HORIZONTAL) positionedMain else lineOffset + crossPosition
964
- val y = if (orientation == HORIZONTAL) lineOffset + crossPosition else positionedMain
973
+ val x = if (orientation == HORIZONTAL) positionedMain.roundToInt() else (lineOffset + crossPosition).roundToInt()
974
+ val y = if (orientation == HORIZONTAL) (lineOffset + crossPosition).roundToInt() else positionedMain.roundToInt()
965
975
  val distributedPrimary = sharedPrimary?.get(index)?.roundToInt() ?: fallbackWidths[index]
966
976
  val w = if (orientation == HORIZONTAL) distributedPrimary else crossSize
967
977
  val h = if (orientation == HORIZONTAL) crossSize else distributedPrimary
968
978
  entry.view.layout(x, y, x + w, y + h)
969
979
  if (sharedPosition == null) mainOffset += distributedPrimary + marginStart + marginEnd + gap + between
970
980
  }
971
- if (sharedCross == null) crossOffset += line.cross + crossBetween
981
+ if (sharedCross == null) crossOffset += lineCross + crossBetween
972
982
  }
973
983
  for (entry in absolute) {
974
984
  if (entry.view is CraftNativeModalView && entry.view.visibility == View.VISIBLE) entry.view.bringToFront()
@@ -981,13 +991,16 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
981
991
  onLayoutChanged?.invoke()
982
992
  }
983
993
 
984
- private fun distribution(free: Int, count: Int): Pair<Int, Int> = when (justifyContent) {
985
- "center" -> free / 2 to 0
986
- "flex-end" -> free to 0
987
- "space-between" -> 0 to if (count > 1) free / (count - 1) else 0
988
- "space-around" -> free / (count * 2) to free / count
989
- "space-evenly" -> free / (count + 1) to free / (count + 1)
990
- else -> 0 to 0
994
+ private fun distribution(free: Float, count: Int): Pair<Float, Float> {
995
+ val available = free
996
+ return when (justifyContent) {
997
+ "center" -> available / 2f to 0f
998
+ "flex-end" -> available to 0f
999
+ "space-between" -> 0f to if (count > 1) available / (count - 1).toFloat() else 0f
1000
+ "space-around" -> available / (count * 2).toFloat() to available / count.toFloat()
1001
+ "space-evenly" -> available / (count + 1).toFloat() to available / (count + 1).toFloat()
1002
+ else -> 0f to 0f
1003
+ }
991
1004
  }
992
1005
  }
993
1006
 
@@ -2721,12 +2734,24 @@ class MainActivity : AppCompatActivity() {
2721
2734
  val minimum = props.optDouble("minimumValue", 0.0)
2722
2735
  val maximumCandidate = props.optDouble("maximumValue", 1.0)
2723
2736
  val maximum = if (maximumCandidate > minimum) maximumCandidate else minimum + Double.MIN_VALUE
2724
- max = 1000
2725
2737
  setOnSeekBarChangeListener(null)
2726
2738
  val step = props.optDouble("step", 0.0).takeIf { it > 0 }
2739
+ // A stepped slider needs one native position per authored
2740
+ // step; a fixed 1,000-position SeekBar silently made large
2741
+ // ranges unable to represent most valid values. Continuous
2742
+ // sliders retain the bounded scale to avoid an enormous max.
2743
+ val progressMax = step?.let {
2744
+ ceil((maximum - minimum) / it).toInt().coerceIn(1, 1_000_000)
2745
+ } ?: 1000
2746
+ max = progressMax
2727
2747
  val requested = props.optDouble("value", minimum)
2728
- val value = if (step != null) minimum + ((requested - minimum) / step).roundToInt() * step else requested
2729
- progress = (((value - minimum) / (maximum - minimum)) * max).roundToInt().coerceIn(0, max)
2748
+ val value = (if (step != null) minimum + ((requested - minimum) / step).roundToInt() * step else requested)
2749
+ .coerceIn(minimum, maximum)
2750
+ progress = if (step != null) {
2751
+ ((value - minimum) / step).roundToInt().coerceIn(0, max)
2752
+ } else {
2753
+ (((value - minimum) / (maximum - minimum)) * max).roundToInt().coerceIn(0, max)
2754
+ }
2730
2755
  if (step != null) screen.sliderSteps[this] = step else screen.sliderSteps.remove(this)
2731
2756
  progressTintList = props.optString("minimumTrackTintColor").takeIf { it.isNotBlank() }
2732
2757
  ?.let { ColorStateList.valueOf(color(it, Color.WHITE)) }
@@ -2742,25 +2767,19 @@ class MainActivity : AppCompatActivity() {
2742
2767
  setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
2743
2768
  override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
2744
2769
  val current = screen.handlers[seekBar] ?: return
2745
- val raw = minimum + (maximum - minimum) * progress.toDouble() / max.toDouble()
2746
2770
  val step = screen.sliderSteps[seekBar]
2747
- val value = if (step != null) minimum + ((raw - minimum) / step).roundToInt() * step else raw
2748
- if (step != null) {
2749
- val snappedProgress = (((value - minimum) / (maximum - minimum)) * max)
2750
- .roundToInt().coerceIn(0, max)
2751
- if (seekBar.progress != snappedProgress) {
2752
- seekBar.progress = snappedProgress
2753
- return
2754
- }
2755
- }
2771
+ val value = (if (step != null) minimum + progress * step else
2772
+ minimum + (maximum - minimum) * progress.toDouble() / max.toDouble())
2773
+ .coerceIn(minimum, maximum)
2756
2774
  send(screen, "EVENT", JSONObject().put("handlerName", current).put("nativeEvent", JSONObject().put("value", value)))
2757
2775
  }
2758
2776
  override fun onStartTrackingTouch(seekBar: SeekBar) {}
2759
2777
  override fun onStopTrackingTouch(seekBar: SeekBar) {
2760
2778
  val current = screen.sliderCompleteHandlers[seekBar] ?: return
2761
- val raw = minimum + (maximum - minimum) * seekBar.progress.toDouble() / max.toDouble()
2762
2779
  val step = screen.sliderSteps[seekBar]
2763
- val value = if (step != null) minimum + ((raw - minimum) / step).roundToInt() * step else raw
2780
+ val value = (if (step != null) minimum + seekBar.progress * step else
2781
+ minimum + (maximum - minimum) * seekBar.progress.toDouble() / max.toDouble())
2782
+ .coerceIn(minimum, maximum)
2764
2783
  send(screen, "EVENT", JSONObject().put("handlerName", current).put("nativeEvent", JSONObject().put("value", value)))
2765
2784
  }
2766
2785
  })