diff --git a/app/src/main/java/net/youapps/calcyou/data/evaluator/Defaults.kt b/app/src/main/java/net/youapps/calcyou/data/evaluator/Defaults.kt index 4f506b2..8fd6a43 100644 --- a/app/src/main/java/net/youapps/calcyou/data/evaluator/Defaults.kt +++ b/app/src/main/java/net/youapps/calcyou/data/evaluator/Defaults.kt @@ -10,17 +10,21 @@ import kotlin.math.atan import kotlin.math.atan2 import kotlin.math.atanh import kotlin.math.ceil +import kotlin.math.cos import kotlin.math.cosh import kotlin.math.exp import kotlin.math.floor +import kotlin.math.hypot import kotlin.math.ln import kotlin.math.log import kotlin.math.log10 import kotlin.math.log2 import kotlin.math.pow import kotlin.math.round +import kotlin.math.sin import kotlin.math.sinh import kotlin.math.sqrt +import kotlin.math.tan import kotlin.math.tanh import kotlin.math.truncate @@ -184,6 +188,63 @@ object Defaults { "ATANH" to { args, mode -> atanh(trigonometricModeToRadian(args.first(), mode)) }, + + // Additional trigonometric functions + "COT" to { args, mode -> + 1.0 / tan(trigonometricModeToRadian(args.first(), mode)) + }, + "SEC" to { args, mode -> + 1.0 / cos(trigonometricModeToRadian(args.first(), mode)) + }, + "CSC" to { args, mode -> + 1.0 / sin(trigonometricModeToRadian(args.first(), mode)) + }, + + // Additional mathematical functions + "CBRT" to { args, _ -> + args.first().pow(1.0 / 3.0) + }, + "HYPOT" to fn@{ args, _ -> + if (args.size == 2) { + return@fn hypot(args[0], args[1]) + } + throw InvalidParameterException("hypot requires two arguments: x, y") + }, + "GCD" to fn@{ args, _ -> + if (args.size == 2) { + val a = args[0].toLong() + val b = args[1].toLong() + return@fn MathUtil.gcd(a, b).toDouble() + } + throw InvalidParameterException("gcd requires two arguments") + }, + "LCM" to fn@{ args, _ -> + if (args.size == 2) { + val a = args[0].toLong() + val b = args[1].toLong() + return@fn MathUtil.lcm(a, b).toDouble() + } + throw InvalidParameterException("lcm requires two arguments") + }, + "CLAMP" to fn@{ args, _ -> + if (args.size == 3) { + val value = args[0] + val min = args[1] + val max = args[2] + return@fn when { + value < min -> min + value > max -> max + else -> value + } + } + throw InvalidParameterException("clamp requires three arguments: value, min, max") + }, + "RADIANS" to { args, _ -> + args.first() * Math.PI / 180.0 + }, + "DEGREES" to { args, _ -> + args.first() * 180.0 / Math.PI + }, ) } } \ No newline at end of file diff --git a/app/src/main/java/net/youapps/calcyou/data/evaluator/MathUtil.kt b/app/src/main/java/net/youapps/calcyou/data/evaluator/MathUtil.kt index ad57ea2..59fcdcc 100644 --- a/app/src/main/java/net/youapps/calcyou/data/evaluator/MathUtil.kt +++ b/app/src/main/java/net/youapps/calcyou/data/evaluator/MathUtil.kt @@ -175,7 +175,11 @@ object MathUtil { * @param maxLen the maximum total length of the resulting string * @param rounding the number of final digits to round */ - fun doubleToString(x: Double, maxLen: Int = MAX_DIGITS, rounding: Int = ROUNDING_DIGITS): String { + fun doubleToString( + x: Double, + maxLen: Int = MAX_DIGITS, + rounding: Int = ROUNDING_DIGITS + ): String { return sizeTruncate(doubleToString(x, rounding), maxLen) } @@ -217,6 +221,23 @@ object MathUtil { return (sqrt((2 * x + 1 / 3) * Math.PI) * x.pow(x) * exp((-x))) } + + // Helper functions for GCD and LCM + fun gcd(a: Long, b: Long): Long { + var x = abs(a) + var y = abs(b) + while (y != 0L) { + val temp = y + y = x % y + x = temp + } + return x + } + + fun lcm(a: Long, b: Long): Long { + return if (a == 0L || b == 0L) 0L else abs(a * b) / gcd(a, b) + } + private const val MAX_DIGITS = 12 private const val ROUNDING_DIGITS = 5 } \ No newline at end of file diff --git a/app/src/main/java/net/youapps/calcyou/data/graphing/Function.kt b/app/src/main/java/net/youapps/calcyou/data/graphing/Function.kt index 3050e0a..db53240 100644 --- a/app/src/main/java/net/youapps/calcyou/data/graphing/Function.kt +++ b/app/src/main/java/net/youapps/calcyou/data/graphing/Function.kt @@ -11,7 +11,8 @@ data class Function( val expression: String, val color: Color, val name: String, - val compiled: CompiledExpression + val compiled: CompiledExpression, + val isVisible: Boolean = true ) { fun execute(variableX: Float, mode: TrigonometricMode, constants: List): Float? { val variables = listOf( diff --git a/app/src/main/java/net/youapps/calcyou/ui/components/AddNewFunctionDialog.kt b/app/src/main/java/net/youapps/calcyou/ui/components/AddNewFunctionDialog.kt index 1b6d688..1bb59fe 100644 --- a/app/src/main/java/net/youapps/calcyou/ui/components/AddNewFunctionDialog.kt +++ b/app/src/main/java/net/youapps/calcyou/ui/components/AddNewFunctionDialog.kt @@ -1,16 +1,28 @@ package net.youapps.calcyou.ui.components import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Help +import androidx.compose.material3.AssistChip +import androidx.compose.material3.Divider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text @@ -32,6 +44,7 @@ import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import net.youapps.calcyou.R import net.youapps.calcyou.viewmodels.GraphViewModel @@ -75,18 +88,33 @@ private fun DialogContent( ) { var color by remember { mutableStateOf(initialColor) } var showColorPicker by remember { mutableStateOf(false) } + var showCheatSheet by remember { mutableStateOf(false) } Box( modifier = modifier .clip(RoundedCornerShape(16.dp)) .background(MaterialTheme.colorScheme.surfaceColorAtElevation(5.dp)) ) { - Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { - Text( - text = stringResource(R.string.add_new_function), - style = MaterialTheme.typography.titleLarge - ) + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(R.string.add_new_function), + style = MaterialTheme.typography.titleLarge + ) + IconButton(onClick = { showCheatSheet = true }) { + Icon( + imageVector = Icons.Rounded.Help, + contentDescription = stringResource(R.string.syntax_help), + tint = MaterialTheme.colorScheme.primary + ) + } + } var text by remember { mutableStateOf(initialExpression) } OutlinedTextField( + modifier = Modifier.fillMaxWidth(), value = text, onValueChange = { text = it @@ -123,6 +151,52 @@ private fun DialogContent( AnimatedVisibility(isError) { Text(text = errorMessage, color = MaterialTheme.colorScheme.error) } + + // Function template chips + Text( + text = stringResource(R.string.quick_templates), + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + val templates = remember { + listOf( + "sin(x)" to "sin(x)", + "x^2" to "x^2", + "sin(cos(x))" to "sin(cos(x))", + "sqrt(x^2+1)" to "sqrt(x^2+1)", + "e^(-x^2)" to "exp(-x^2)", + "ln(abs(x))" to "ln(abs(x))", + "tan(x/2)" to "tan(x/2)", + "cbrt(x)" to "cbrt(x)", + "cot(x)" to "cot(x)", + "1/x" to "1/x" + ) + } + LazyRow( + modifier = Modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + items(templates, key = { it.first }) { (template, label) -> + + AssistChip( + onClick = { + text = template + checkExpression(template) + }, + label = { + Text( + text = label, + style = TextStyle( + fontFamily = FontFamily.Serif, + fontStyle = FontStyle.Italic, + fontSize = 12.sp + ) + ) + } + ) + } + } Row(Modifier.align(Alignment.End)) { TextButton(onClick = onCancel) { Text(text = stringResource(id = android.R.string.cancel)) @@ -136,14 +210,207 @@ private fun DialogContent( } } if (showColorPicker) { - ColorSelectionDialog(initialColor = remember { color }, + ColorSelectionDialog( + initialColor = remember { color }, onDismiss = { showColorPicker = false }) { color = it } } + if (showCheatSheet) { + SyntaxCheatSheetDialog(onDismiss = { showCheatSheet = false }) + } } +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun SyntaxCheatSheetDialog(onDismiss: () -> Unit) { + Dialog(onDismiss) { + Box( + modifier = Modifier + .clip(RoundedCornerShape(16.dp)) + .background(MaterialTheme.colorScheme.surfaceColorAtElevation(5.dp)) + .heightIn(max = 600.dp) + ) { + LazyColumn( + modifier = Modifier + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + item { + Text( + text = stringResource(R.string.function_syntax_reference), + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.primary + ) + + } + item { + CheatSheetSection( + title = stringResource(R.string.operators), + items = listOf( + "+" to stringResource(R.string.addition), + "-" to stringResource(R.string.subtraction), + "*" to stringResource(R.string.multiplication), + "/" to stringResource(R.string.division), + "^" to stringResource(R.string.exponentiation_x_2), + "%" to stringResource(R.string.modulo_remainder) + ) + ) + } + item { + CheatSheetSection( + title = stringResource(R.string.basic_functions), + items = listOf( + "abs(x)" to stringResource(R.string.absolute_value), + "sqrt(x)" to stringResource(R.string.square_root), + "cbrt(x)" to stringResource(R.string.cube_root), + "exp(x)" to stringResource(R.string.exponential_e_x), + "ln(x)" to stringResource(R.string.natural_logarithm), + "log10(x)" to stringResource(R.string.base_10_logarithm), + "log2(x)" to stringResource(R.string.base_2_logarithm), + "log(x; base)" to stringResource(R.string.logarithm_with_custom_base), + "pow(base; exp)" to stringResource(R.string.power_function) + ) + ) + } + + item { + CheatSheetSection( + title = stringResource(R.string.trigonometric_functions), + items = listOf( + "sin(x), cos(x), tan(x)" to stringResource(R.string.basic_trig), + "asin(x), acos(x), atan(x)" to stringResource(R.string.inverse_trig), + "sinh(x), cosh(x), tanh(x)" to stringResource(R.string.hyperbolic), + "asinh(x), acosh(x), atanh(x)" to stringResource(R.string.inverse_hyperbolic), + "cot(x), sec(x), csc(x)" to stringResource(R.string.cotangent_secant_cosecant) + ) + ) + } + + item { + CheatSheetSection( + title = stringResource(R.string.rounding_functions), + items = listOf( + "ceil(x)" to stringResource(R.string.round_up), + "floor(x)" to stringResource(R.string.round_down), + "round(x)" to stringResource(R.string.round_to_nearest), + "truncate(x)" to stringResource(R.string.remove_decimals) + ) + ) + } + item { + CheatSheetSection( + title = stringResource(R.string.advanced_functions), + items = listOf( + "fac(n)" to stringResource(R.string.factorial_n), + "min(a; b; ...)" to stringResource(R.string.minimum_value), + "max(a; b; ...)" to stringResource(R.string.maximum_value), + "sign(x)" to stringResource(R.string.sign_1_0_or_1), + "hypot(x; y)" to stringResource(R.string.hypotenuse_sqrt_x_y), + "gcd(a; b)" to stringResource(R.string.greatest_common_divisor), + "lcm(a; b)" to stringResource(R.string.least_common_multiple), + "clamp(val; min; max)" to stringResource(R.string.clamp_value), + "radians(deg)" to stringResource(R.string.degrees_to_radians), + "degrees(rad)" to stringResource(R.string.radians_to_degrees) + ) + ) + } + + item { + CheatSheetSection( + title = stringResource(R.string.constants), + items = listOf( + "pi or π" to stringResource(R.string.pi_3_14159), + "e" to stringResource(R.string.euler_s_number_2_71828) + ) + ) + } + + + item { + CheatSheetSection( + title = stringResource(R.string.nested_functions), + items = listOf( + "sin(cos(x))" to stringResource(R.string.functions_inside_functions), + "sqrt(x^2 + 1)" to stringResource(R.string.combine_operations), + "exp(-x^2)" to stringResource(R.string.gaussian_like_curves), + "ln(abs(x))" to stringResource(R.string.avoid_domain_errors) + ) + ) + } + + item { + Divider() + } + + item { + Text( + text = stringResource(R.string.note_use_semicolons_to_separate_multiple_arguments), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + fontStyle = FontStyle.Italic + ) + } + + item { + Text( + text = stringResource(R.string.supports_nested_functions_like_sin_cos_x), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + fontStyle = FontStyle.Italic + ) + } + + + item { + TextButton( + onClick = onDismiss, + modifier = Modifier + ) { + Text(text = stringResource(R.string.close)) + } + } + + } + } + } +} + +@Composable +private fun CheatSheetSection(title: String, items: List>) { + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.secondary + ) + items.forEach { (syntax, description) -> + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = syntax, + style = TextStyle( + fontFamily = FontFamily.Monospace, + fontSize = 13.sp, + color = MaterialTheme.colorScheme.primary + ), + modifier = Modifier.weight(0.5f) + ) + Text( + text = description, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.weight(0.5f) + ) + } + } + } +} + @Preview(showBackground = true) @Composable private fun DialogContentPreview() { @@ -152,7 +419,7 @@ private fun DialogContentPreview() { onCancel = {}, checkExpression = {}, isError = true, - errorMessage = "Invalid token at index 0", + errorMessage = stringResource(R.string.invalid_token_at_index_0), functionName = "f" ) } \ No newline at end of file diff --git a/app/src/main/java/net/youapps/calcyou/ui/screens/graphing/CanvasView.kt b/app/src/main/java/net/youapps/calcyou/ui/screens/graphing/CanvasView.kt index 902c8e2..df2a293 100644 --- a/app/src/main/java/net/youapps/calcyou/ui/screens/graphing/CanvasView.kt +++ b/app/src/main/java/net/youapps/calcyou/ui/screens/graphing/CanvasView.kt @@ -7,16 +7,33 @@ import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.calculateCentroid import androidx.compose.foundation.gestures.calculatePan import androidx.compose.foundation.gestures.calculateZoom +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.drawscope.scale import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.positionChange +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel import net.youapps.calcyou.data.graphing.pxToUnitCoordinates import net.youapps.calcyou.viewmodels.GraphViewModel @@ -93,6 +110,59 @@ fun CanvasView(vm: GraphViewModel) { renderCanvas(vm.window, vm, scale, textMeasurer, gridLinesColor, gridAxesColor) } } + + // Legend overlay showing visible functions + if (vm.functions.isNotEmpty()) { + GraphLegend( + modifier = Modifier + .align(Alignment.TopEnd) + .padding(16.dp), + functions = vm.functions.filter { it.isVisible } + ) + } + } +} + +@Composable +fun GraphLegend( + modifier: Modifier = Modifier, + functions: List +) { + Surface( + modifier = modifier, + shape = RoundedCornerShape(8.dp), + color = MaterialTheme.colorScheme.surface.copy(alpha = 0.85f), + shadowElevation = 4.dp + ) { + Column( + modifier = Modifier.padding(8.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + functions.forEach { function -> + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp) + ) { + Box( + modifier = Modifier + .size(12.dp) + .clip(CircleShape) + .background(function.color) + ) + Text( + text = "${function.name}(x) = ${function.expression}", + style = TextStyle( + fontFamily = FontFamily.Serif, + fontWeight = FontWeight.Medium, + fontSize = 12.sp, + fontStyle = FontStyle.Italic, + color = MaterialTheme.colorScheme.onSurface + ), + maxLines = 1 + ) + } + } + } } } diff --git a/app/src/main/java/net/youapps/calcyou/ui/screens/graphing/GraphingScreen.kt b/app/src/main/java/net/youapps/calcyou/ui/screens/graphing/GraphingScreen.kt index 79ac17f..ffaa4c7 100644 --- a/app/src/main/java/net/youapps/calcyou/ui/screens/graphing/GraphingScreen.kt +++ b/app/src/main/java/net/youapps/calcyou/ui/screens/graphing/GraphingScreen.kt @@ -15,6 +15,8 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Clear +import androidx.compose.material.icons.rounded.Visibility +import androidx.compose.material.icons.rounded.VisibilityOff import androidx.compose.material3.BottomSheetScaffold import androidx.compose.material3.Button import androidx.compose.material3.Divider @@ -81,6 +83,9 @@ fun GraphingScreen(graphViewModel: GraphViewModel = viewModel()) { onClickRemoveFunction = { graphViewModel.removeFunction(it) }, + onToggleFunctionVisibility = { + graphViewModel.toggleFunctionVisibility(it) + }, onClickConstant = { graphViewModel.selectedConstantIndex = it showAddConstantDialog = true @@ -158,6 +163,7 @@ fun FunctionConstantsList( constants: List, onClickFunction: (Int) -> Unit, onClickRemoveFunction: (Int) -> Unit, + onToggleFunctionVisibility: (Int) -> Unit, onClickConstant: (Int) -> Unit, onClickRemoveConstant: (Int) -> Unit ) { @@ -169,9 +175,13 @@ fun FunctionConstantsList( lhsName = "${function.name}(x)", text = function.expression, color = function.color, + isVisible = function.isVisible, onClick = { onClickFunction(index) }, onClickRemove = { onClickRemoveFunction(index) + }, + onToggleVisibility = { + onToggleFunctionVisibility(index) } ) Divider(Modifier.fillMaxWidth()) @@ -195,7 +205,9 @@ fun FunctionRow( text: String, color: Color?, onClick: () -> Unit, - onClickRemove: () -> Unit + onClickRemove: () -> Unit, + isVisible: Boolean = true, + onToggleVisibility: (() -> Unit)? = null ) { Row( modifier = Modifier @@ -223,6 +235,15 @@ fun FunctionRow( ) ) Spacer(modifier = Modifier.weight(1f)) + if (onToggleVisibility != null) { + IconButton(onClick = { onToggleVisibility() }) { + Icon( + imageVector = if (isVisible) Icons.Rounded.Visibility else Icons.Rounded.VisibilityOff, + contentDescription = if (isVisible) "Hide function" else "Show function", + tint = if (isVisible) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outline + ) + } + } IconButton(onClick = { onClickRemove() }) { Icon( imageVector = Icons.Rounded.Clear, diff --git a/app/src/main/java/net/youapps/calcyou/ui/screens/graphing/GraphingUtils.kt b/app/src/main/java/net/youapps/calcyou/ui/screens/graphing/GraphingUtils.kt index 5aa2540..e32fc99 100644 --- a/app/src/main/java/net/youapps/calcyou/ui/screens/graphing/GraphingUtils.kt +++ b/app/src/main/java/net/youapps/calcyou/ui/screens/graphing/GraphingUtils.kt @@ -24,22 +24,26 @@ fun DrawScope.drawGridLines(window: Window, lineWidth: Float, gridLinesColor: Co floor(window.xMax / window.xScale).toInt() ) + // Draw vertical grid lines with subtle styling for (i in xRange) { if (i == 0) continue val xDraw = Offset(i * window.xScale, 0f).unitToPxCoordinates(window, size.width, size.height).x - drawLine(gridLinesColor, Offset(xDraw, 0f), Offset(xDraw, size.height), lineWidth) + val alpha = if (i % 5 == 0) 0.3f else 0.15f // Emphasize every 5th line + drawLine(gridLinesColor.copy(alpha = alpha), Offset(xDraw, 0f), Offset(xDraw, size.height), lineWidth) } val yRange = IntRange( ceil(window.yMin / window.yScale).toInt(), floor(window.yMax / window.yScale).toInt() ) + // Draw horizontal grid lines with subtle styling for (i in yRange) { if (i == 0) continue val yDraw = Offset(0f, i * window.yScale).unitToPxCoordinates(window, size.width, size.height).y - drawLine(gridLinesColor, Offset(0f, yDraw), Offset(size.width, yDraw), lineWidth) + val alpha = if (i % 5 == 0) 0.3f else 0.15f // Emphasize every 5th line + drawLine(gridLinesColor.copy(alpha = alpha), Offset(0f, yDraw), Offset(size.width, yDraw), lineWidth) } } @@ -71,20 +75,20 @@ fun DrawScope.drawAxes( axesColor: Color ) { - // y-axis + // y-axis - draw with slightly thicker line for prominence val windowCenterInCanvas = Offset(0f, 0f).unitToPxCoordinates(window, size.width, size.height) drawLine( - axesColor, + axesColor.copy(alpha = 0.8f), Offset(windowCenterInCanvas.x, 0f), Offset(windowCenterInCanvas.x, size.height), - lineWidth + lineWidth * 1.5f ) - // x-axis + // x-axis - draw with slightly thicker line for prominence drawLine( - axesColor, + axesColor.copy(alpha = 0.8f), Offset(0f, windowCenterInCanvas.y), Offset(size.width, windowCenterInCanvas.y), - lineWidth + lineWidth * 1.5f ) // Ticks on x-axis @@ -290,10 +294,14 @@ fun DrawScope.renderCanvas( drawGridLines(window, lineWidth, gridLinesColor) drawAxes(window, lineWidth, canvasScale, textMeasurer, axesColor) + // Draw functions with slightly thicker lines for better visibility + val graphLineWidth = lineWidth * 1.8f vm.functions.forEach { // this might fail if we attempt to use a constant that no longer exists - runCatching { - drawGraph(window, it, vm.constants, lineWidth, vm.mode) + if (it.isVisible) { + runCatching { + drawGraph(window, it, vm.constants, graphLineWidth, vm.mode) + } } } } \ No newline at end of file diff --git a/app/src/main/java/net/youapps/calcyou/viewmodels/GraphViewModel.kt b/app/src/main/java/net/youapps/calcyou/viewmodels/GraphViewModel.kt index 8142118..1b026a0 100644 --- a/app/src/main/java/net/youapps/calcyou/viewmodels/GraphViewModel.kt +++ b/app/src/main/java/net/youapps/calcyou/viewmodels/GraphViewModel.kt @@ -106,6 +106,11 @@ class GraphViewModel(private val application: Application) : AndroidViewModel(ap } } + fun toggleFunctionVisibility(index: Int) { + val function = functions[index] + functions[index] = function.copy(isVisible = !function.isVisible) + } + private fun getFuncName(index: Int): String { return Defaults.defaultFuncNameChars[ index % Defaults.defaultFuncNameChars.size diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8a86398..3dcc603 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -181,4 +181,58 @@ ASCII Base Currencies + Supports nested functions like sin(cos(x)) + Operators + Addition + Subtraction + Multiplication + Division + Exponentiation (x^2) + Modulo (remainder) + Basic Functions + Absolute value + Square root + Cube root + Exponential (e^x) + Natural logarithm + Base-10 logarithm + Base-2 logarithm + Logarithm with custom base + Power function + Basic trig + Inverse trig + Hyperbolic + Inverse hyperbolic + Cotangent, secant, cosecant + Rounding Functions + Round up + Round down + Round to nearest + Remove decimals + Advanced Functions + Factorial (n!) + Minimum value + Maximum value + Sign (-1, 0, or 1) + Hypotenuse sqrt(x²+y²) + Greatest common divisor + Least common multiple + Clamp value + Degrees to radians + Radians to degrees + Pi (3.14159...) + Euler\'s number (2.71828...) + Functions inside functions + Combine operations + Gaussian-like curves + Avoid domain errors + Note: Use semicolons (;) to separate multiple arguments + Close + Invalid token at index 0 + Quick Templates: + Constants + Nested Functions + Syntax help + Function Syntax Reference + Trigonometric Functions