I would like to animate the opacity value of a text string containing the name of the level in and out with a delay in the middle.
So the sequence of events would be like:
Start transparent
Fade in to solid white over a second of game time
Wait a second
Fade out to transparent again over a second.
The code I have written to animate the alpha value isn't working. Plus, it's pretty ugly and I'm sure there's a better way to do it using the XNA framework.
I've been unable to find any advice elsewhere about doing this. Surely animating values like this isn't that uncommon. How can I do it?
Here's my current code as requested (yes it's horrible).
private int fadeStringDirection = +1;
private int fadeStringDuration = 1000;
private float stringAlpha = 0;
private int stringRef = 0;
private int stringPhase = 1;
...
if (!pause)
{
totalMillisecondsElapsed += gameTime.ElapsedGameTime.Milliseconds;
if (fadestringDirection != 0)
{
stringAlpha = ((float)(totalMillisecondsElapsed - stringRef) / (float)(fadeStringDuration*stringPhase)) * fadeStringDirection;
stringAlpha = MathHelper.Clamp(stringAlpha, 0, 1);
if (topAlpha / 2 + 0.5 == fadeStringDirection)
{
fadeStringDirection = 0;
stringRef = totalMillisecondsElapsed;
stringPhase++;
}
}
else
{
stringRef += gameTime.ElapsedGameTime.Milliseconds;
if (stringRef >= fadeStringDuration * stringPhase)
{
stringPhase++;
fadeStringDirection = -1;
stringRef = totalMillisecondsElapsed;
}
}
}
Here's the solution I have now. Much nicer than what I had before (and in a class of its own).
/// <summary>
/// Animation helper class.
/// </summary>
public class Animation
{
List<Keyframe> keyframes = new List<Keyframe>();
int timeline;
int lastFrame = 0;
bool run = false;
int currentIndex;
/// <summary>
/// Construct new animation helper.
/// </summary>
public Animation()
{
}
public void AddKeyframe(int time, float value)
{
Keyframe k = new Keyframe();
k.time = time;
k.value = value;
keyframes.Add(k);
keyframes.Sort(delegate(Keyframe a, Keyframe b) { return a.time.CompareTo(b.time); });
lastFrame = (time > lastFrame) ? time : lastFrame;
}
public void Start()
{
timeline = 0;
currentIndex = 0;
run = true;
}
public void Update(GameTime gameTime, ref float value)
{
if (run)
{
timeline += gameTime.ElapsedGameTime.Milliseconds;
value = MathHelper.SmoothStep(keyframes[currentIndex].value, keyframes[currentIndex + 1].value, (float)timeline / (float)keyframes[currentIndex + 1].time);
if (timeline >= keyframes[currentIndex + 1].time && currentIndex != keyframes.Count) { currentIndex++; }
if (timeline >= lastFrame) { run = false; }
}
}
public struct Keyframe
{
public int time;
public float value;
}
}
Related
I have a widget made up of 2 TextViews, 2 ImageViews, and 2 TextClocks. I am trying to update the widget using a ScreenListener (code below) and an alarm. I want all views to update when the screen is turned On. Also, a repeating alarm is set when the screen is turned On. The alarm is used to trigger the update of the battery level indicator (an ImageView). The image for the battery level indicator is generated using the ProgressRing (code below). The alarm is cancelled when the screen is turned Off. The TextClocks take care of themselves.
Everything works when debugging using the emulator or my phone. The various components update when I turn the screen On and Off. However, when I install the widget on my phone, the widget stops updating after a period of time. Initially, I can turn the screen On and Off and everything updates. And, I can plug (or unplug) the phone and the alarm will update the battery level indicator. But, after some period of time (I think with the screen Off), updating stops. Turning the screen On no longer updates anything and, I beleive, the alarm is no longer set. (Note: The TextClock continue to work and show the correct time.)
When debugging, I have seen the instances of the ScreenLister and ProgressRing become null. So, I've included checks for this and make new instances when it happens. This doesn't seem like the right solution. Should this be occurring?
Is the system killing my widget? Is there a way around this, if so? Is my AppWidgetProvider somehow losing it's connection to the RemoteViews?
Thanks for any help. And, let me know if you need more information.
P.S. I'm currently building for Android 9.0. And, I'm new to Android programming.
AppWidgetProvider
namespace ClockCalBattery
{
[BroadcastReceiver(Enabled = true)]
[IntentFilter(new string[] { "android.appwidget.action.APPWIDGET_UPDATE", Intent.ActionUserPresent})]
[MetaData("android.appwidget.provider", Resource = "#xml/appwidgetprovider")]
public class CCBWidget : AppWidgetProvider, ScreenStateListener
{
public static ProgressRing progressRing = new ProgressRing();
private static String ACTION_UPDATE_BATTERY = "com.lifetree.clockcalbattery.UPDATE-BATTERY";
public static ScreenListener mScreenListener;
public static int n_alarms = 0;
public override void OnReceive(Context context, Intent intent)
{
base.OnReceive(context, intent);
if (intent.Action == ACTION_UPDATE_BATTERY)
{
update_batteryLevel(context);
if (mScreenListener == null)
{
mScreenListener = new ScreenListener(context);
mScreenListener.begin(this);
}
if (progressRing == null)
{
progressRing = new ProgressRing();
}
}
}
public override void OnEnabled(Context context)
{
base.OnEnabled(context);
if (mScreenListener == null)
{
mScreenListener = new ScreenListener(context);
mScreenListener.begin(this);
}
if (progressRing == null)
{
progressRing = new ProgressRing();
}
update_batteryLevel(Application.Context);
update_nextAlarm(Application.Context);
}
public override void OnDisabled(Context context)
{
base.OnDisabled(context);
}
public override void OnUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
{
base.OnUpdate(context, appWidgetManager, appWidgetIds);
mScreenListener = new ScreenListener(context);
progressRing = new ProgressRing();
var me = new ComponentName(context, Java.Lang.Class.FromType(typeof(CCBWidget)).Name);
var widgetView = BuildRemoteViews(context, appWidgetIds);
appWidgetManager.UpdateAppWidget(me, widgetView);
}
private RemoteViews BuildRemoteViews(Context context, int[] appWidgetIds)
{
RemoteViews widgetView = new RemoteViews(context.PackageName, Resource.Layout.CCBWidget);
update_TodaysDate(widgetView);
update_nextAlarm(context, widgetView);
update_batteryLevel(widgetView);
return widgetView;
}
private void update_TodaysDate(RemoteViews widgetView)
{
string today = DateTime.Today.ToString("D");
widgetView.SetTextViewText(Resource.Id.longDate, today);
}
private void update_nextAlarm(Context context, RemoteViews widgetView)
{
AlarmManager am = (AlarmManager)context.GetSystemService(Context.AlarmService);
AlarmManager.AlarmClockInfo alarmInfo = am.NextAlarmClock;
if (alarmInfo != null)
{
long time = alarmInfo.TriggerTime;
DateTime alarmDateTime = new DateTime(1970, 1, 1).AddMilliseconds(time).ToLocalTime();
string alarmTime = alarmDateTime.ToString("ddd ");
alarmTime += alarmDateTime.ToString("t");
widgetView.SetTextViewText(Resource.Id.textView_alarmTime, alarmTime);
widgetView.SetImageViewResource(Resource.Id.imageView_alarmIcon, Resource.Drawable.whiteOnBlack_clock);
}
else
{
widgetView.SetTextViewText(Resource.Id.textView_alarmTime, "");
widgetView.SetImageViewResource(Resource.Id.imageView_alarmIcon, Resource.Drawable.navigation_empty_icon);
}
}
private void update_nextAlarm(Context mContext)
{
RemoteViews widgetView = new RemoteViews(mContext.PackageName, Resource.Layout.CCBWidget);
update_nextAlarm(mContext, widgetView);
var me = new ComponentName(mContext, Java.Lang.Class.FromType(typeof(CCBWidget)).Name);
var awm = AppWidgetManager.GetInstance(mContext);
awm.UpdateAppWidget(me, widgetView);
}
private void update_batteryLevel(RemoteViews widgetView)
{
progressRing.setProgress((int)(Battery.ChargeLevel * 100.0));
progressRing.drawProgressBitmap();
widgetView.SetImageViewBitmap(Resource.Id.progressRing, progressRing.ring);
}
private void update_batteryLevel(Context mContext)
{
RemoteViews widgetView = new RemoteViews(mContext.PackageName, Resource.Layout.CCBWidget);
update_batteryLevel(widgetView);
var me = new ComponentName(mContext, Java.Lang.Class.FromType(typeof(CCBWidget)).Name);
var awm = AppWidgetManager.GetInstance(mContext);
awm.UpdateAppWidget(me, widgetView);
}
public static void turnUpdateAlarmOnOff(Context context, bool turnOn, int time)
{
AlarmManager alarmManager = (AlarmManager)context.GetSystemService(Context.AlarmService);
Intent intent = new Intent(context, typeof(CCBWidget));
intent.SetAction(ACTION_UPDATE_BATTERY);
PendingIntent pendingIntent = PendingIntent.GetBroadcast(context, 0, intent, 0);
if (turnOn)
{
// Add extra 1 sec because sometimes ACTION_BATTERY_CHANGED is called after the first alarm
alarmManager.SetInexactRepeating(AlarmType.ElapsedRealtime, SystemClock.ElapsedRealtime() + 1000, time * 1000, pendingIntent);
n_alarms++;
}
else
{
alarmManager.Cancel(pendingIntent);
n_alarms--;
}
}
public void onScreenOn()
{
if (mScreenListener == null)
{
mScreenListener = new ScreenListener(Application.Context);
mScreenListener.begin(this);
}
if (progressRing == null)
{
progressRing = new ProgressRing();
}
update_batteryLevel(Application.Context);
update_nextAlarm(Application.Context);
turnUpdateAlarmOnOff(Application.Context, true, 60);
}
public void onScreenOff()
{
turnUpdateAlarmOnOff(Application.Context, false, 1);
}
public void onUserPresent()
{
//Console.WriteLine("onUserPresent");
}
}
}
ScreenListener
namespace ClockCalBattery
{
public class ScreenListener
{
private Context mContext;
private ScreenBroadcastReceiver mScreenReceiver;
private static ScreenStateListener mScreenStateListener;
public ScreenListener(Context context)
{
mContext = context;
mScreenReceiver = new ScreenBroadcastReceiver();
}
/**
* screen BroadcastReceiver
*/
private class ScreenBroadcastReceiver : BroadcastReceiver
{
private String action = null;
public override void OnReceive(Context context, Intent intent)
{
action = intent.Action;
if (Intent.ActionScreenOn == action)
{ // screen on
mScreenStateListener.onScreenOn();
}
else if (Intent.ActionScreenOff == action)
{ // screen off
mScreenStateListener.onScreenOff();
}
else if (Intent.ActionUserPresent == action)
{ // unlock
mScreenStateListener.onUserPresent();
}
}
}
/**
* begin to listen screen state
*
* #param listener
*/
public void begin(ScreenStateListener listener)
{
mScreenStateListener = listener;
registerListener();
getScreenState();
}
/**
* get screen state
*/
private void getScreenState()
{
PowerManager manager = (PowerManager)mContext
.GetSystemService(Context.PowerService);
if (manager.IsInteractive)
{
if (mScreenStateListener != null)
{
mScreenStateListener.onScreenOn();
}
}
else
{
if (mScreenStateListener != null)
{
mScreenStateListener.onScreenOff();
}
}
}
/**
* stop listen screen state
*/
public void unregisterListener()
{
mContext.UnregisterReceiver(mScreenReceiver);
}
/**
* regist screen state broadcast
*/
private void registerListener()
{
IntentFilter filter = new IntentFilter();
filter.AddAction(Intent.ActionScreenOn);
filter.AddAction(Intent.ActionScreenOff);
filter.AddAction(Intent.ActionUserPresent);
mContext.ApplicationContext.RegisterReceiver(mScreenReceiver, filter);
//mContext.RegisterReceiver(mScreenReceiver, filter);
}
public interface ScreenStateListener
{// Returns screen status information to the caller
void onScreenOn();
void onScreenOff();
void onUserPresent();
}
}
}
ProgressRing
namespace ClockCalBattery
{
public class ProgressRing
{
private int max = 100;
public int progress;
private Path path = new Path();
Color color = new Color(50, 50, 255, 255);
private Paint paint;
private Paint mPaintProgress;
private RectF mRectF;
private Paint batteryLevelTextPaint;
private Paint batteryStateTextPaint;
private String batteryLevelText = "0%";
private String batteryStateText = null;
private BatteryState bs;
private Rect textBounds = new Rect();
private int centerY;
private int centerX;
private float swipeAndgle = 0;
public Bitmap ring;
private int bitmapWidth = 70;
private int bitmapHeight = 70;
public ProgressRing()
{
progress = -1;
paint = new Paint();
paint.AntiAlias = true;
paint.StrokeWidth = 1;
paint.SetStyle(Paint.Style.Stroke);
paint.Color = color;
mPaintProgress = new Paint();
mPaintProgress.AntiAlias = true;
mPaintProgress.SetStyle(Paint.Style.Stroke);
mPaintProgress.StrokeWidth = 5;
mPaintProgress.Color = color;
batteryLevelTextPaint = new Paint();
batteryLevelTextPaint.AntiAlias = true;
batteryLevelTextPaint.SetStyle(Paint.Style.Fill);
batteryLevelTextPaint.Color = color;
batteryLevelTextPaint.StrokeWidth = 1;
batteryStateTextPaint = new Paint();
batteryStateTextPaint.AntiAlias = true;
batteryStateTextPaint.SetStyle(Paint.Style.Fill); ;
batteryStateTextPaint.Color = color;
batteryStateTextPaint.StrokeWidth = 1;
//batteryStateTextPaint.SetTypeface(Typeface.Create(Typeface.Default, TypefaceStyle.Bold));
Init_ring();
}
private void Init_ring()
{
int viewWidth = bitmapWidth;
int viewHeight = bitmapHeight;
float radius = (float)(bitmapHeight / 2.0);
path.Reset();
centerX = viewWidth / 2;
centerY = viewHeight / 2;
path.AddCircle(centerX, centerY, radius, Path.Direction.Cw);
float smallCirclRadius = radius - (float)(0.1 * radius);
path.AddCircle(centerX, centerY, smallCirclRadius, Path.Direction.Cw);
//mRectF = new RectF(0, 0, viewWidth, viewHeight);
mRectF = new RectF(centerX - smallCirclRadius, centerY - smallCirclRadius, centerX + smallCirclRadius, centerY + smallCirclRadius);
batteryLevelTextPaint.TextSize = radius * 0.5f;
batteryStateTextPaint.TextSize = radius * 0.30f;
}
internal void setProgress(int progress)
{
this.progress = progress;
int percentage = progress * 100 / max;
swipeAndgle = percentage * 360 / 100;
batteryLevelText = percentage + "%";
batteryStateText = null;
bs = Battery.State;
if (bs == BatteryState.Charging)
batteryStateText = "Charging";
else if (percentage > 99)
batteryStateText = "Full";
else if (percentage < 15)
batteryStateText = "Low";
}
internal void drawProgressBitmap()
{
ring = Bitmap.CreateBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.Argb8888);
Canvas c = new Canvas(ring);
byte r = 0;
byte g = 0;
byte b = 0;
byte a = 255;
hls2rgb(swipeAndgle * (120.0 / 360.0), 1, 128, ref r, ref g, ref b);
paint.Color = new Color(r, g, b, a);
mPaintProgress.Color = new Color(r, g, b, a);
batteryLevelTextPaint.Color = new Color(r, g, b, a);
batteryStateTextPaint.Color = new Color(r, g, b, a);
c.DrawArc(mRectF, 270, swipeAndgle, false, mPaintProgress);
drawTextCentred(c);
}
private void drawTextCentred(Canvas c)
{
batteryLevelTextPaint.GetTextBounds(batteryLevelText, 0, batteryLevelText.Length, textBounds);
c.DrawText(batteryLevelText, centerX - textBounds.ExactCenterX(), centerY - textBounds.ExactCenterY(), batteryLevelTextPaint);
if (batteryStateText != null)
{
batteryStateTextPaint.GetTextBounds(batteryStateText, 0, batteryStateText.Length, textBounds);
c.DrawText(batteryStateText, centerX - textBounds.ExactCenterX(), centerY - textBounds.ExactCenterY() + 15, batteryStateTextPaint);
}
}
public void hls2rgb(double color_wheel_angle, double tlen, byte ilum,
ref byte color_r, ref byte color_g, ref byte color_b)
{
double rlum, rm1, rm2;
if (ilum > 255) ilum = 255;
if (ilum < 0) ilum = 0;
if (tlen > 1) tlen = 1;
if (tlen < 0) tlen = 0;
rlum = (double)ilum / 255;
if (rlum < 0.5)
rm2 = rlum * (1.0 + tlen);
else
rm2 = rlum + tlen - (rlum * tlen);
rm1 = (2.0 * rlum) - rm2;
if (tlen == 0)
{
color_r = ilum;
color_g = ilum;
color_b = ilum;
}
else
{
color_g = (byte)(value(rm1, rm2, color_wheel_angle + 120) * 255);
color_b = (byte)(value(rm1, rm2, color_wheel_angle) * 255);
color_r = (byte)(value(rm1, rm2, color_wheel_angle - 120) * 255);
}
return;
}
public double value(double r1, double r2, double angle)
{
double value;
angle = angle - 120;
if (angle > 360) angle = angle - 360;
if (angle < 0) angle = angle + 360;
if (angle < 60)
value = r1 + (r2 - r1) * angle / 60;
else if (angle < 180)
value = r2;
else if (angle < 240)
value = r1 + (r2 - r1) * (240 - angle) / 60;
else
value = r1;
return (value);
}
}
}
I want to zoom in & out an image in blazor on asp.net.
As I use for Google Maps, I want to move the image position by zooming and dragging the image with the mouse wheel.(I want to use an image file, not a Google map.)
Is there a way to zoom in, zoom out and drag specific images in blazor?
Note:
I would only use this on BlazorWasm and not BlazorServer because there might be quite a bit of lag if the network is slow.
It is probably easier to just use JavaScript and/or JavaScript interoperability (JS interop) but for this example I decided not to use JS Interop.
This component enables you to zoom by pressing shift while mouse wheel up (zoom out) or mouse wheel down (zoom in) and move the image by pressing mouse button 1 down while moving. (it's more panning than dragging it)
Restriction in Blazor: (at the time of writing this)
The biggest issue at the moment is not having access to the mouse OffsetX and OffsetY within the html element as described here and also here, so the moving of the image has to be done using CSS only.
The reason I used Shift for scrolling is because scrolling is not being blocked or disabled as described here even with #onscroll:stopPropagation, #onwheel:stopPropagation, #onmousewheel:stopPropagation and/or #onscroll:preventDefault, #onwheel:preventDefault, #onmousewheel:preventDefault set on the parent mainImageContainer element. The screen will still scroll left and right if the content is wider than the viewable page.
Solution:
The Zooming part is pretty straight forward, all you need to do is set the transform:scale(n); property in the #onmousewheel event.
The moving of the image is a bit more complex because there is no reference to where the mouse pointer is in relation to the image or element boundaries. (OffsetX and OffsetY)
The only thing we can determine is if a mouse button is pressed and then calculate what direction the mouse is moving in left, right, up or down.
The idea is then to move the position of element with the image by setting the top and left CSS values as percentages.
This component code:
#using System.Text;
<div id="mainImageContainer" style="display: block;width:#($"{ImageWidthInPx}px");height:#($"{ImageHeightInPx}px");overflow: hidden;">
<div id="imageMover"
#onmousewheel="MouseWheelZooming"
style="#MoveImageStyle">
<div id="imageContainer"
#onmousemove="MouseMoving"
style="#ZoomImageStyle">
#*this div is used just for moving around when zoomed*#
</div>
</div>
</div>
#if (ShowResetButton)
{
<div style="display:block">
<button #onclick="ResetImgage">Reset</button>
</div>
}
#code{
/// <summary>
/// The path or url of the image
/// </summary>
[Parameter]
public string ImageUrlPath { get; set; }
/// <summary>
/// The width of the image
/// </summary>
[Parameter]
public int ImageWidthInPx { get; set; }
/// <summary>
/// The height of the image
/// </summary>
[Parameter]
public int ImageHeightInPx { get; set; }
/// <summary>
/// Set to true to show the reset button
/// </summary>
[Parameter]
public bool ShowResetButton { get; set; }
/// <summary>
/// Set the amount the image is scaled by, default is 0.1f
/// </summary>
[Parameter]
public double DefaultScaleBy { get; set; } = 0.1f;
/// <summary>
/// The Maximum the image can scale to, default = 5f
/// </summary>
[Parameter]
public double ScaleToMaximum { get; set; } = 5f;
/// <summary>
/// Set the speed at which the image is moved by, default 2.
/// 2 or 3 seems to work best.
/// </summary>
[Parameter]
public double DefaultMoveBy { get; set; } = 2;
//defaults
double _CurrentScale = 1.0f;
double _PositionLeft = 0;
double _PositionTop = 0;
double _OldClientX = 0;
double _OldClientY = 0;
double _DefaultMinPosition = 0;//to the top and left
double _DefaultMaxPosition = 0;//to the right and down
//the default settings used to display the image in the child div
private Dictionary<string, string> _ImageContainerStyles;
Dictionary<string, string> ImageContainerStyles
{
get
{
if (_ImageContainerStyles == null)
{
_ImageContainerStyles = new Dictionary<string, string>();
_ImageContainerStyles.Add("width", "100%");
_ImageContainerStyles.Add("height", "100%");
_ImageContainerStyles.Add("position", "relative");
_ImageContainerStyles.Add("background-size", "contain");
_ImageContainerStyles.Add("background-repeat", "no-repeat");
_ImageContainerStyles.Add("background-position", "50% 50%");
_ImageContainerStyles.Add("background-image", $"URL({ImageUrlPath})");
}
return _ImageContainerStyles;
}
}
private Dictionary<string, string> _MovingContainerStyles;
Dictionary<string, string> MovingContainerStyles
{
get
{
if (_MovingContainerStyles == null)
{
InvokeAsync(ResetImgage);
}
return _MovingContainerStyles;
}
}
protected async Task ResetImgage()
{
_PositionLeft = 0;
_PositionTop = 0;
_DefaultMinPosition = 0;
_DefaultMaxPosition = 0;
_CurrentScale = 1.0f;
_MovingContainerStyles = new Dictionary<string, string>();
_MovingContainerStyles.Add("width", "100%");
_MovingContainerStyles.Add("height", "100%");
_MovingContainerStyles.Add("position", "relative");
_MovingContainerStyles.Add("left", $"{_PositionLeft}%");
_MovingContainerStyles.TryAdd("top", $"{_PositionTop}%");
await InvokeAsync(StateHasChanged);
}
string ZoomImageStyle { get => DictionaryToCss(ImageContainerStyles); }
string MoveImageStyle { get => DictionaryToCss(MovingContainerStyles); }
private string DictionaryToCss(Dictionary<string, string> styleDictionary)
{
StringBuilder sb = new StringBuilder();
foreach (var kvp in styleDictionary.AsEnumerable())
{
sb.AppendFormat("{0}:{1};", kvp.Key, kvp.Value);
}
return sb.ToString();
}
protected async void MouseMoving(MouseEventArgs e)
{
//if the mouse button 1 is not down exit the function
if (e.Buttons != 1)
{
_OldClientX = e.ClientX;
_OldClientY = e.ClientY;
return;
}
//get the % of the current scale to move by at least the default move speed plus any scaled changes
//basically the bigger the image the faster it moves..
double scaleFrac = (_CurrentScale / ScaleToMaximum);
double scaleMove = (DefaultMoveBy * (DefaultMoveBy * scaleFrac));
//moving mouse right
if (_OldClientX < e.ClientX)
{
if ((_PositionLeft - DefaultMoveBy) <= _DefaultMaxPosition)
{
_PositionLeft += scaleMove;
}
}
//moving mouse left
if (_OldClientX > e.ClientX)
{
//if (_DefaultMinPosition < (_PositionLeft - DefaultMoveBy))
if ((_PositionLeft + DefaultMoveBy) >= _DefaultMinPosition)
{
_PositionLeft -= scaleMove;
}
}
//moving mouse down
if (_OldClientY < e.ClientY)
{
//if ((_PositionTop + DefaultMoveBy) <= _DefaultMaxPosition)
if ((_PositionTop - DefaultMoveBy) <= _DefaultMaxPosition)
{
_PositionTop += scaleMove;
}
}
//moving mouse up
if (_OldClientY > e.ClientY)
{
//if ((_PositionTop - DefaultMoveBy) > _DefaultMinPosition)
if ((_PositionTop + DefaultMoveBy) >= _DefaultMinPosition)
{
_PositionTop -= scaleMove;
}
}
_OldClientX = e.ClientX;
_OldClientY = e.ClientY;
await UpdateScaleAndPosition();
}
async Task<double> IncreaseScale()
{
return await Task.Run(() =>
{
//increase the scale first then calculate the max and min positions
_CurrentScale += DefaultScaleBy;
double scaleFrac = (_CurrentScale / ScaleToMaximum);
double scaleDiff = (DefaultMoveBy + (DefaultMoveBy * scaleFrac));
double scaleChange = DefaultMoveBy + scaleDiff;
_DefaultMaxPosition += scaleChange;
_DefaultMinPosition -= scaleChange;
return _CurrentScale;
});
}
async Task<double> DecreaseScale()
{
return await Task.Run(() =>
{
_CurrentScale -= DefaultScaleBy;
double scaleFrac = (_CurrentScale / ScaleToMaximum);
double scaleDiff = (DefaultMoveBy + (DefaultMoveBy * scaleFrac));
double scaleChange = DefaultMoveBy + scaleDiff;
_DefaultMaxPosition -= scaleChange;
_DefaultMinPosition += scaleChange;//DefaultMoveBy;
//fix descaling, move the image back into view when descaling (zoomin out)
if (_CurrentScale <= 1)
{
_PositionLeft = 0;
_PositionTop = 0;
}
else
{
//left can not be more than max position
_PositionLeft = (_DefaultMaxPosition < _PositionLeft) ? _DefaultMaxPosition : _PositionLeft;
//top can not be more than max position
_PositionTop = (_DefaultMaxPosition < _PositionTop) ? _DefaultMaxPosition : _PositionTop;
//left can not be less than min position
_PositionLeft = (_DefaultMinPosition > _PositionLeft) ? _DefaultMinPosition : _PositionLeft;
//top can not be less than min position
_PositionTop = (_DefaultMinPosition > _PositionTop) ? _DefaultMinPosition : _PositionTop;
}
return _CurrentScale;
});
}
protected async void MouseWheelZooming(WheelEventArgs e)
{
//holding shift stops the page from scrolling
if (e.ShiftKey == true)
{
if (e.DeltaY > 0)
{
_CurrentScale = ((_CurrentScale + DefaultScaleBy) >= 5) ? _CurrentScale = 5f : await IncreaseScale();
}
if (e.DeltaY < 0)
{
_CurrentScale = ((_CurrentScale - DefaultScaleBy) <= 0) ? _CurrentScale = DefaultScaleBy : await DecreaseScale();
}
await UpdateScaleAndPosition();
}
}
/// <summary>
/// Refresh the values in the moving style dictionary that is used to position the image.
/// </summary>
async Task UpdateScaleAndPosition()
{
await Task.Run(() =>
{
if (!MovingContainerStyles.TryAdd("transform", $"scale({_CurrentScale})"))
{
MovingContainerStyles["transform"] = $"scale({_CurrentScale})";
}
if (!MovingContainerStyles.TryAdd("left", $"{_PositionLeft}%"))
{
MovingContainerStyles["left"] = $"{_PositionLeft}%";
}
if (!MovingContainerStyles.TryAdd("top", $"{_PositionTop}%"))
{
MovingContainerStyles["top"] = $"{_PositionTop}%";
}
});
}
}
This is the usage:
#page "/"
#using BlazorWasmApp.Components
Welcome to your new app.
<ZoomableImageComponent ImageUrlPath="images/Capricorn.png"
ImageWidthInPx=400
ImageHeightInPx=300
ShowResetButton=true
DefaultScaleBy=0.1f />
and this is the result:
I only tested this in chrome on a desktop computer without touch input.
I'm creating a procedurally generated platformer in the C# console, out of interest. However, I found that the program runs slower than I would expect unless I hold any button, then it returns to the expected amount entered as timeLapse.
Currently the only button with a functionality is the space bar, which makes you jump. I tested the code as well without the code on line // THIS CODE, and that removes the problem. Does anyone have an explanation or an article about this? Couldn't find a lot myself.
PlatformGenerator code could be added, although I don't think it would be necessary.
Program code:
namespace TestProject.Procedural_Generation
{
public class PlatformManager
{
#region Variables
// Game settings
private static char[,] screen;
private static int screenWidth = 96;
private static int screenHeight = 20;
// Platform generation settings
private static int minGeneration = 4;
private static int maxGeneration = 18;
private static PlatformGenerator pg;
private static List<Platform> platforms = new List<Platform>();
// Physics variables
private static int oldX = 0;
private static int currentX = 0;
private static int currentY = 8;
private static string toSkip = "";
private static Player player;
// Timer variables
private static float timeLapse = 100;
private static float jumpTime = 200;
private static Timer graphicUpdater;
private static Timer physicUpdater;
private static Timer jumpTimer;
private static bool isJumping = false;
private static long score = 0;
#endregion
public PlatformManager()
{
screen = new char[screenHeight, screenWidth];
pg = new PlatformGenerator(minGeneration, maxGeneration);
player = new Player();
// Physics
physicUpdater = new Timer(timeLapse);
physicUpdater.Elapsed += ExecutePhysics;
physicUpdater.AutoReset = true;
physicUpdater.Enabled = true;
jumpTimer = new Timer(jumpTime);
jumpTimer.Elapsed += Land;
jumpTimer.Enabled = true;
// Graphics
graphicUpdater = new Timer(timeLapse);
graphicUpdater.Elapsed += ExecuteGraphics;
graphicUpdater.AutoReset = true;
graphicUpdater.Enabled = true;
while (true)
{
score++;
}
}
private void Land(object source, EventArgs e)
{
isJumping = false;
}
#region Graphics
private void ExecuteGraphics(object source, ElapsedEventArgs e)
{
Console.Clear();
Console.Write(Display());
}
// Makes sure everything is printed correctly
public string Display()
{
string result = "";
for (int y = 0; y < screenHeight; y++)
{
for (int x = 0; x < screenWidth; x++)
{
if (y == player.y && x == player.x)
{
result += "O";
}
else if (screen[y, x] == '=')
{
result += "=";
}
else
{
result += " ";
}
}
result += "\n";
}
return result;
}
#endregion
#region Physics
// Controls platform generation and movement
private void ExecutePhysics(object source, ElapsedEventArgs e)
{
Platformer();
if (player.y == screenHeight - 1)
{
graphicUpdater.Stop();
physicUpdater.Stop();
Console.WriteLine();
Console.WriteLine("Game over! Score: " + Math.Floor(Math.Sqrt(score)));
}
else if (isJumping)
{
player.y -= 1;
}
else if (Console.ReadKey(true).Key == ConsoleKey.Spacebar && screen[player.y + 1, player.x] == '=') // THIS CODE
{
isJumping = true;
jumpTimer.Start();
player.y -= 1;
}
else if (screen[player.y + 1, player.x] != '=' && !isJumping)
{
player.y += 1;
}
}
// Generate a new platform
public void Platformer()
{
Platform newPlatform = pg.Generate(currentX, currentY);
currentY = newPlatform.y;
if (currentX + newPlatform.size + newPlatform.xDif > screenWidth)
{
MoveScreen(newPlatform.size + newPlatform.xDif);
currentX -= newPlatform.size + newPlatform.xDif;
oldX -= newPlatform.size + newPlatform.xDif;
}
while (currentX < oldX + newPlatform.size + newPlatform.xDif)
{
screen[currentY, currentX] = '=';
currentX += 1;
}
oldX = currentX;
}
// Update all rows so the newly added ones fit.
public void MoveScreen(int amount)
{
for (int y = 0; y < screenHeight; y++)
{
for (int x = amount; x < screenWidth; x++)
{
screen[y, x - amount] = screen[y, x];
}
}
for (int y = 0; y < screenHeight; y++)
{
for (int x = screenWidth - amount; x < screenWidth; x++)
{
screen[y, x] = '\0';
}
}
}
#endregion
}
}
Not sure If I understand correclty, but the
Console.ReadKey(true).Key == ConsoleKey.Spacebar
waits for a keypress to happen, so the code will not be processed until a key is actually pressed, if you remove that the programm will obviously move "faster" because it does not wait for a keypress to happen
I am currently working on a XP Leveling system in unity. At the moment my code works (this first part is inside the onclick method):
currentExp = Xp.LevelToXP(combat + 1);
if (combatExp + 5 < currentExp)
{
combatExp += 5;
if (previousExp == 0)
{
float fill = (float)(currentExp) / 100;
XpBar.fillAmount += (5 / fill) / 100;
}
else
{
float fill = (float)(currentExp - previousExp) / 100;
XpBar.fillAmount += (5 / fill) / 100;
}
}
else if (combatExp + 5 == currentExp)
{
combatExp += 5;
combat++;
previousExp = currentExp;
XpBar.fillAmount = 0;
}
else if (combatExp + 5 > currentExp)
{
combatExp += 5;
combat++;
previousExp = currentExp;
XpBar.fillAmount = 0;
float remainingExp = (float)combatExp - currentExp;
XpBar.fillAmount += (remainingExp / currentExp) / 100f;
}
txtCombatLvl.text = "Combat Level: " + combat;
this is what happens when you press the button.
but when i try to put all of this in a method instead of the onclick method
void AddExp (int skill, int skillExp, int expAmount)
{
currentExp = Xp.LevelToXP(skill + 1);
if (skillExp + expAmount < currentExp)
{
skillExp += expAmount;
if (previousExp == 0)
{
float fill = (float)(currentExp) / 100;
XpBar.fillAmount += (expAmount / fill) / 100;
}
else
{
float fill = (float)(currentExp - previousExp) / 100;
XpBar.fillAmount += (expAmount / fill) / 100;
}
}
else if (skillExp + expAmount == currentExp)
{
skillExp += expAmount;
skill++;
previousExp = currentExp;
XpBar.fillAmount = 0;
}
else if (skillExp + expAmount > currentExp)
{
skillExp += expAmount;
skill++;
previousExp = currentExp;
XpBar.fillAmount = 0;
float remainingExp = (float)skillExp - currentExp;
XpBar.fillAmount += (remainingExp / currentExp) / 100f;
}
}
And I try to access it by calling it like this:
AddExp(combat, combatExp, 5);
none of my combat xp and level don't save. Can someone point me in the right direction since none of what i tried worked.
The parameters of a C# method are passed by value and not by references, so changes to them inside a function are local to the function. If you want the values updated then you prefix them with the ref keyword.
void AddExp (ref int skill, ref int skillExp, int expAmount)
Alternate Solution
Alternatively, you could simplify your design with an encapsulation of your concept of a Skill with a class. Here's a possibility, I couldn't quite follow your math and am not familiar with your other classes, so you can adjust this as necessary.
public class Skill
{
private string _name;
private int _currentValue;
private int _currentLevel = 1;
public Skill(string name) {
}
public string Name { get { return _name; } }
public int CurrentXp { get { return _currentValue; } }
public int CurrentLevel { get { return _currentLevel; } }
public int XpRequiredForNextLevel { get { return Xp.LevelToXP(_currentLevel + 1); } }
public int XpRequiredForCurrentLevel { get { return Xp.LevelToXP(_currentLevel); } }
private float CalculateFillPercentage(int xp)
{
var xpInLevel = XpRequiredForNextLevel - XpRequiredForCurrentLevel;
return (float)(xp - XpRequiredForCurrentLevel) / (float)xpInLevel;
}
public void AddXp(int expAmount)
{
_currentValue += expAmount;
int XpInCurrentLevel = XpRequiredForNextLevel - XpRequiredForCurrentLevel;
if (_currentValue > XpRequiredForNextLevel) {
} else {
_currentLevel++;
}
XpBar.fillAmount = CalculateFillPercentage (_currentValue);
}
}
You could use this by holding instances of each skill, for example:
Skill combat = new Skill("combat");
And then later
combat.AddXp(50);
Each skill would be self contained. I'm not sure about the XpBar or how that is controlled between different types of skills, but you could further encapsulate by calculating internally, so the skill provides everything your UI needs to keep up to date.
I wrote this for a meteorology class. For some reason, the textboxes aren't updating on the GUI correctly for large numbers (large numbers of photons). The calculation completes, but the textboxes don't update.
I suspect the problem is with calling Invoke(), but I can't for the life of me see what's going wrong. I've tried using both Invoke() and BeginInvoke() with similar results.
Can anyone help figure out where I'm going wrong?
Thanks!
PS> Please forgive the global variables. Was planning on cleaning them up later...
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace CloudTransmittance
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void buttonCalculate_Click(object sender, EventArgs e)
{
Thread t = new Thread(calculateModel);
t.Start();
}
//number of photons that have gone to albedo, direct, or diffuse transmittance
private ulong top = 0;
private ulong direct = 0;
private ulong diffuse = 0;
private ulong absorbed = 0;
private ulong failed = 0;
private ulong photons = 0;
private void calculateModel()
{
//model variables
double theta = 0;
double tauStar = 0;
double omega = 0;
double g = 0;
photons = 0;
//Get data from form
theta = Convert.ToDouble(textBoxTheta.Text);
tauStar = Convert.ToDouble(textBoxTau.Text);
omega = Convert.ToDouble(textBoxOmega.Text);
g = Convert.ToDouble(textBoxG.Text);
photons = Convert.ToUInt64(textBoxPhotons.Text);
//Clear the progress bar and set its limits
this.progressBar1.BeginInvoke(
(MethodInvoker)delegate()
{
this.progressBar1.Minimum = 0;
this.progressBar1.Value = 0;
this.progressBar1.Maximum = (int)photons;
this.progressBar1.Step = 1;
});
//Clear the text boxes
this.textBoxAlbedo.Invoke(
(MethodInvoker)delegate()
{
this.textBoxAlbedo.Text = "";
});
this.textBoxDirect.Invoke(
(MethodInvoker)delegate()
{
this.textBoxDirect.Text = "";
});
this.textBoxDiffuse.Invoke(
(MethodInvoker)delegate()
{
this.textBoxDiffuse.Text = "";
});
this.textBox1.Invoke(
(MethodInvoker)delegate()
{
this.textBox1.Text = "";
});
this.textBox2.Invoke(
(MethodInvoker)delegate()
{
this.textBox2.Text = "";
});
//convert theta to radians from degrees
theta *= Math.PI / 180;
//number of photons that have gone to albedo, direct, or diffuse transmittance
top = 0;
direct = 0;
diffuse = 0;
absorbed = 0;
failed = 0;
//Random number generator
Random r = new Random();
double randomValue = 0;
int count = 1000; //number of iterations of the problem...
double delta = 0.00001; //close enough to "1" for calculations, since C# random goes from [0, 1) instead of [0, 1]
//Calculate transmittance
for (ulong photonCount = 0; photonCount < photons; photonCount++)
{
bool scattered = false;
double newTheta = theta; //needed for looping
int i = 0; //counting variable used to prevent infinite looping
for (i = 0; i < count; i++)
{
double length = calculateTauP(); //length of the photon's travel
double newTau = calculateTau(newTheta, length);
if (newTau < 0)
{
top++; //photon has exited through the top
break; //move to the next photon
}
else if (newTau > tauStar)
{
//exited through the bottom of the cloud
if (scattered == false)
{
//direct transmittance
direct++;
}
else
{
//diffuse transmittance
diffuse++;
}
break;
}
else
{
//photon is either scattered or absorbed
randomValue = r.NextDouble();
if (randomValue >= omega) // || ((omega == 1) && (randomValue >= (omega - delta)) )
{
//photon absorbed, no longer of interest
absorbed++;
break;
}
else
{
//photon scattered, determine direction
scattered = true;
newTheta = calculateNewAngle(newTau, newTheta, g, randomValue);
}
}
}
if (i >= count)
{
failed++;
}
this.progressBar1.BeginInvoke(
(MethodInvoker)delegate()
{
this.progressBar1.PerformStep();
});
}
//Update Form values
displayData();
}
private void displayData()
{
if (this.textBoxAlbedo.InvokeRequired)
{
this.textBoxAlbedo.Invoke(
(MethodInvoker)delegate()
{
this.textBoxAlbedo.Text = ((double)top / (double)photons).ToString();
});
}
else
{
textBoxAlbedo.Text = ((double)top / (double)photons).ToString();
}
if (this.textBoxDirect.InvokeRequired)
{
this.textBoxDirect.Invoke(
(MethodInvoker)delegate()
{
this.textBoxDirect.Text = ((double)direct / (double)photons).ToString();
});
}
else
{
textBoxDirect.Text = ((double)direct / (double)photons).ToString();
}
if (this.textBoxDiffuse.InvokeRequired)
{
this.textBoxDiffuse.Invoke(
(MethodInvoker)delegate()
{
this.textBoxDiffuse.Text = ((double)diffuse / (double)photons).ToString();
});
}
else
{
textBoxDiffuse.Text = ((double)diffuse / (double)photons).ToString();
}
if (this.textBox1.InvokeRequired)
{
this.textBox1.Invoke(
(MethodInvoker)delegate()
{
this.textBox1.Text = absorbed.ToString();
});
}
else
{
textBox1.Text = absorbed.ToString();
}
if (this.textBox2.InvokeRequired)
{
this.textBox2.Invoke(
(MethodInvoker)delegate()
{
this.textBox2.Text = failed.ToString();
});
}
else
{
textBox2.Text = failed.ToString();
}
}
private double calculateNewAngle(double length, double angle, double g, double randomNumber)
{
double newAngle = 0;
double cos = (1 / (2 * g)) * (1 + Math.Pow(g, 2) - Math.Pow(((1 - Math.Pow(g, 2)) / (1 + g * (2 * randomNumber - 1))), 2));
newAngle += angle + cos;
while (newAngle >= 2 * Math.PI)
{
newAngle -= 2 * Math.PI; //normalize the angle to 0 <= angle < 2PI
}
return newAngle;
}
private double calculateTauP()
{
Random r = new Random();
double distance = -1 * Math.Log(1 - r.NextDouble());
return distance;
}
private double calculateTau(double angle, double tauP)
{
double tau = tauP * Math.Cos(Math.PI/2 - angle);
return tau;
}
}
}
Stop using Invoke and BeginInvoke to update the UI. Despite what you may have been told it is not that great of solution. Actually, in situations like these where all you want to to do is update the UI with progress information it is probably the worst solution. Instead, have your worker thread publish its progress information to an immutable data structure that can be shared with the UI thread. Then have your UI thread poll for it on a reasonable interval using a System.Windows.Forms.Timer.
public class YourForm : Form
{
private class ProgressInfo
{
public ProgressInfo(ulong top, ulong direct, ulong diffuse, ulong absorbed, ulong failed, ulong photons)
{
// Set properties here.
}
public ulong Top { get; private set; }
public ulong Direct { get; private set; }
public ulong Diffuse { get; private set; }
public ulong Dbsorbed { get; private set; }
public ulong Failed { get; private set; }
public ulong Photons { get; private set; }
}
private volatile ProgressInfo progress = null;
private void calculateModel()
{
for (ulong photonCount = 0; photonCount < photons; photonCount++)
{
// Do your calculations here.
// Publish new progress information.
progress = new ProgressInfo(/* ... */);
}
}
private void UpdateTimer_Tick(object sender, EventArgs args)
{
// Get a local reference to the data structure.
// This is all that is needed since ProgressInfo is immutable
// and the member was marked as volatile.
ProgressInfo local = progress;
this.textBoxAlbedo.Text = ((double)local.Top / (double)local.Photons).ToString();
this.textBoxDirect.Text = ((double)local.Direct / (double)local.Photons).ToString();
this.textBoxDiffuse.Text = ((double)local.Diffuse / (double)local.Photons).ToString();
this.textBox1.Text = local.Absorbed.ToString();
this.textBox2.Text = local.Failed.ToString();
}
Notice several things here.
The code is a lot easier to understand and follow.
The UI thread gets to decide when and how often its controls should be updated.
You get more throughput on the worker thread since it does not have to wait for a response from the UI as would occur with Invoke.
I rip on these Invoke and BeginInvoke solutions a lot because in many cases they are terrible solutions. Using a BackgroundWorker is a little better, but it still forces you into the push method of updating the UI (via the same marshaling techniques behind the scenes nonetheless). The pull method can be (and often is) a more elegant solution and usually more efficient.